Data

We will use the same dataset as Project 1: movies_merged.

Objective

Your goal in this project is to build a linear regression model that can predict the Gross revenue earned by a movie based on other variables. You may use R packages to fit and evaluate a regression model (no need to implement regression yourself). Please stick to linear regression, however.

Instructions

You should be familiar with using an RMarkdown Notebook by now. Remember that you have to open it in RStudio, and you can run code chunks by pressing Cmd+Shift+Enter.

Please complete the tasks below and submit this R Markdown file (as pr2.Rmd) containing all completed code chunks and written responses, and a PDF export of it (as pr2.pdf) which should include the outputs and plots as well.

Note that Setup and Data Preprocessing steps do not carry any points, however, they need to be completed as instructed in order to get meaningful results.

Setup

Same as Project 1, load the dataset into memory:

setwd("~/git/GeorgiaTech/cse6242/pr2")
load('movies_merged')

This creates an object of the same name (movies_merged). For convenience, you can copy it to df and start using it:

df = movies_merged
cat("Dataset has", dim(df)[1], "rows and", dim(df)[2], "columns", end="\n", file="")
Dataset has 40789 rows and 39 columns 
colnames(df)
 [1] "Title"             "Year"              "Rated"            
 [4] "Released"          "Runtime"           "Genre"            
 [7] "Director"          "Writer"            "Actors"           
[10] "Plot"              "Language"          "Country"          
[13] "Awards"            "Poster"            "Metascore"        
[16] "imdbRating"        "imdbVotes"         "imdbID"           
[19] "Type"              "tomatoMeter"       "tomatoImage"      
[22] "tomatoRating"      "tomatoReviews"     "tomatoFresh"      
[25] "tomatoRotten"      "tomatoConsensus"   "tomatoUserMeter"  
[28] "tomatoUserRating"  "tomatoUserReviews" "tomatoURL"        
[31] "DVD"               "BoxOffice"         "Production"       
[34] "Website"           "Response"          "Budget"           
[37] "Domestic_Gross"    "Gross"             "Date"             
Title

Year

Rated

Released

Runtime

Genre

Director

Writer

Actors

Plot

Language

Country

Awards

Poster

Metascore

imdbRating

imdbVotes

imdbID

Type

tomatoMeter

tomatoImage

tomatoRating

tomatoReviews

tomatoFresh

tomatoRotten

tomatoConsensus

tomatoUserMeter

tomatoUserRating

tomatoUserReviews

tomatoURL

DVD

BoxOffice

Production

Website

Response

Budget

Domestic_Gross

Gross

Date

Load R packages

Load any R packages that you will need to use. You can come back to this chunk, edit it and re-run to load any additional packages later.

library(GGally)  # Used for ggpairs
library(ggplot2)
library(lubridate)  # Used to extract year from Released

Attaching package: 'lubridate'
The following object is masked from 'package:base':

    date
library(qdapTools)  # Used for mtabulate (to one-hot encode a column)
library(reshape)  # Used for melt function

Attaching package: 'reshape'
The following object is masked from 'package:lubridate':

    stamp
library(stringi)  # Used for processing Awards

If you are using any non-standard packages (ones that have not been discussed in class or explicitly allowed for this project), please mention them below. Include any special instructions if they cannot be installed using the regular install.packages('<pkg name>') command.

Non-standard packages used: None

Data Preprocessing

Before we start building models, we should clean up the dataset and perform any preprocessing steps that may be necessary. Some of these steps can be copied in from your Project 1 solution. It may be helpful to print the dimensions of the resulting dataframe at each step.

1. Remove non-movie rows

# TODO: Remove all rows from df that do not correspond to movies
df <- df[df$Type == "movie",]

2. Drop rows with missing Gross value

Since our goal is to model Gross revenue against other variables, rows that have missing Gross values are not useful to us.

# TODO: Remove rows with missing Gross value
df = df[which(!is.na(df$Gross)), ]

3. Exclude movies released prior to 2000

Inflation and other global financial factors may affect the revenue earned by movies during certain periods of time. Taking that into account is out of scope for this project, so let’s exclude all movies that were released prior to the year 2000 (you may use Released, Date or Year for this purpose).

# TODO: Exclude movies released prior to 2000
df$released_year = year(df$Released)
df = df[which(df$released_year >= 2000), ]

4. Eliminate mismatched rows

Note: You may compare the Released column (string representation of release date) with either Year or Date (numeric representation of the year) to find mismatches. The goal is to avoid removing more than 10% of the rows.

# TODO: Remove mismatched rows
# This is just taken from my work in pr1; only drop if the difference in the years is 2 or greater.
df$not_matched = (abs(df$Year != df$released_year) >= 2)

# Number of rows with non-null Gross Value
print(nrow(df))
[1] 3325
print(nrow(df[which(!is.na(df$Gross)), ]))
[1] 3325
# Remove mismatched rows
df = df[which(!(df$not_matched == TRUE)), ]
print(nrow(df))
[1] 3325

5. Drop Domestic_Gross column

Domestic_Gross is basically the amount of revenue a movie earned within the US. Understandably, it is very highly correlated with Gross and is in fact equal to it for movies that were not released globally. Hence, it should be removed for modeling purposes.

# TODO: Exclude the `Domestic_Gross` column
df$Domestic_Gross = NULL

6. Process Runtime column

# TODO: Replace df$Runtime with a numeric column containing the runtime in minutes
# Taking work from pr1
extract_runtime = function(r){
    times = unlist(r)
    minutes = 0
    for (i in 1:length(times) - 1){
        if (times[i + 1] == 'h'){
            minutes = minutes + as.numeric(times[i]) * 60
        } else if (times[i + 1] == 'min'){
            minutes = minutes + as.numeric(times[i])
        }
    }
    if (minutes == 0){
        return(NA)
    } else{
        return(minutes)
    }
}

y=strsplit(df$Runtime,' ')
new_runtimes = unlist(lapply(y, extract_runtime))
df$Runtime = new_runtimes

Perform any additional preprocessing steps that you find necessary, such as dealing with missing values or highly correlated columns (feel free to add more code chunks, markdown blocks and plots here as necessary).

# TODO(optional): Additional preprocessing

# Plotting Correlation Plots of the different Rating Columns:
ggpairs(df[c('imdbRating', 'tomatoRating', 'tomatoUserRating', 'tomatoMeter')])

# Plotting Correlation Plots of the different Review number Columns:
ggpairs(df[c('imdbVotes', 'tomatoReviews', 'tomatoUserReviews')])

# Remove tomato-like Columns to avoid high variance in models.
df$tomatoMeter = NULL
df$tomatoRating = NULL
df$tomatoReviews = NULL
df$tomatoFresh = NULL
df$tomatoRotten = NULL
df$tomatoUserMeter = NULL
df$tomatoUserRating = NULL
df$tomatoUserReviews = NULL

# Remove Year since we will use released_year column
df$Year = NULL

# Also creating categorical value for runtime, just as we did in Project 1 to explore relationships
df$runtime_categorical = ifelse(df$Runtime <= 68, 'short', ifelse(df$Runtime <= 101, 'medium', 'long'))

From the above, notice that imdbRating is very highly correlated with tomatoRating, tomatoUserRating and tomatoMeter. Thus, to avoid high variance in the models, it makes more sense to just include imdbRating as a proxy for the rating of the movie.

When plotting a similar plot for user reviews, it again, it looks like imdbVotes is highly correlated with tomatoReviews. Furthermore, while imdbVotes is not as correlated with tomatoUserReviews, there does seem to be still sufficient linear correlation. Based on these two plots, in this assignment, we’re going to remove all the ‘tomato’ columns to avoid unnecessary potentially high variance in models (ala bias-variance tradeoff). It seems that both imdbRating and imdbVotes capture enough information along these dimensions.

We will also remove movies released in 2017. Notice that if we look at the distribution of movies by year:

table(df$released_year)

2000 2001 2002 2003 2004 2005 2006 2007 2008 2009 2010 2011 2012 2013 2014 
 172  173  186  184  183  189  223  205  220  197  221  226  209  205  224 
2015 2016 2017 
 207   97    4 

notice that only 4 movies were released in 2017 in our data set. This is probably just a consequence of when this data was pulled. It probably is not representative to include this data, since this is not actually representative of movies in 2017. Thus, we will remove these 4 observations

df = df[which(df$released_year < 2017), ]

Note: Do NOT convert categorical variables (like Genre) into binary columns yet. You will do that later as part of a model improvement task.

Final preprocessed dataset

Report the dimensions of the preprocessed dataset you will be using for modeling and evaluation, and print all the final column names. (Again, Domestic_Gross should not be in this list!)

# TODO: Print the dimensions of the final preprocessed dataset and column names
cat("Dataset has", dim(df)[1], "rows and", dim(df)[2], "columns", end="\n", file="")
Dataset has 3321 rows and 32 columns 
colnames(df)
 [1] "Title"               "Rated"               "Released"           
 [4] "Runtime"             "Genre"               "Director"           
 [7] "Writer"              "Actors"              "Plot"               
[10] "Language"            "Country"             "Awards"             
[13] "Poster"              "Metascore"           "imdbRating"         
[16] "imdbVotes"           "imdbID"              "Type"               
[19] "tomatoImage"         "tomatoConsensus"     "tomatoURL"          
[22] "DVD"                 "BoxOffice"           "Production"         
[25] "Website"             "Response"            "Budget"             
[28] "Gross"               "Date"                "released_year"      
[31] "not_matched"         "runtime_categorical"
Title

Rated

Released

Runtime

Genre

Director

Writer

Actors

Plot

Language

Country

Awards

Poster

Metascore

imdbRating

imdbVotes

imdbID

Type

tomatoImage

tomatoConsensus

tomatoURL

DVD

BoxOffice

Production

Website

Response

Budget

Gross

Date

released_year

not_matched

runtime_categorical

Evaluation Strategy

In each of the tasks described in the next section, you will build a regression model. In order to compare their performance, you will compute the training and test Root Mean Squared Error (RMSE) at different training set sizes.

First, randomly sample 10-20% of the preprocessed dataset and keep that aside as the test set. Do not use these rows for training! The remainder of the preprocessed dataset is your training data.

Now use the following evaluation procedure for each model:

You can define a helper function that applies this procedure to a given set of features and reuse it.

# Setting up Training and Test DF
split_train_test_df = function(df, train_split=0.8){
  # Given a DataFrame, randomly split to training and test DataFrame
  # Now Selecting 80% of data as sample from total 'n' rows of the data
  set.seed(100)
  sample <- sample.int(n = nrow(df), size = floor(.8*nrow(df)), replace = F)
  train_df <- df[sample, ]
  test_df  <- df[-sample, ]
  return(list('train_df'=train_df,
              'test_df'=test_df,
              'train_idx'=sample))
}

split_df = split_train_test_df(df, train_split=0.8)
train_df = split_df$train_df
test_df = split_df$test_df
train_idx = split_df$train_idx

# Create training set size vector
train_sizes = c(0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1)
# Helper Functions
getrmse = function(mod, test_df){
    # Helper function to return both the training RMSE and test RMSE given model and test DataFrame
    # From https://stackoverflow.com/questions/43123462/how-to-obtain-rmse-out-of-lm-result
    train_rss = c(crossprod(mod$residuals))
    train_mse = train_rss / length(mod$residuals)
    train_rmse = sqrt(train_mse)
    
    test_rmse = sqrt(mean((test_df$Gross - predict.lm(mod, test_df)) ^ 2, na.rm=TRUE))
    
    return(list('train_rmse'=train_rmse,
                'test_rmse'=test_rmse))
}
# Helper function to build and evaluate model according to the instructions provided
eval_model = function(train_df, test_df, model_exp, train_sizes, num_repeat=10){
  # Keyword Args:
  #  train_df: Training DataFrame
  #  test_df: Test DataFrame
  #  model_exp: Expression of linear regression model as a string
  #  num_repeat: Number of times to repeat evaluating the model (since there might be high variance)
  # Returns: df_eval which is a DataFrame with test and train rmse
  train_rmse_all = c()
  test_rmse_all = c()
  for(train_size in train_sizes){
    train_rmse_one_size = c()
    test_rmse_one_size = c()
    for(r in 1:num_repeat){
      sample <- sample.int(n = nrow(train_df), size = floor(train_size*nrow(train_df)), replace = F)
      train_df_subset = train_df[sample, ]
      
      # Training and evaluating model
      mod1 = lm(model_exp, data=train_df_subset)
      model_eval = getrmse(mod1, test_df)
      train_rmse = model_eval$train_rmse
      test_rmse = model_eval$test_rmse
      
      train_rmse_one_size = c(train_rmse_one_size, train_rmse)
      test_rmse_one_size = c(test_rmse_one_size, test_rmse)
    }
    train_rmse_all = c(train_rmse_all, mean(train_rmse_one_size))
    test_rmse_all = c(test_rmse_all, mean(test_rmse_one_size))
  }
  
  rmse_type = c(rep('train', length(train_sizes)), rep('test', length(train_sizes)))
  rmses = c(train_rmse_all, test_rmse_all)
  
  df_eval = data.frame(train_size=train_sizes, rmse_type=rmse_type, rmses=rmses)
  best_test_rmse = min(df_eval[df_eval$rmse_type=='test', 'rmses'])
  best_train_size = train_sizes[which.min(df_eval[df_eval$rmse_type=='test', 'rmses'])]

  return(list('df_eval'=df_eval,
              'best_test_rmse'=best_test_rmse,
              'best_train_size'=best_train_size))  
}

Tasks

Each of the following tasks is worth 20 points, for a total of 100 points for this project. Remember to build each model as specified, evaluate it using the strategy outlined above, and plot the training and test errors by training set size (%).

1. Numeric variables

Use Linear Regression to predict Gross based on available numeric variables. You can choose to include all or a subset of them.

# TODO: Build & evaluate model 1 (numeric variables only)
numeric_cols = colnames(df[, sapply(df, is.numeric)])
print(numeric_cols)
[1] "Runtime"       "imdbRating"    "imdbVotes"     "Budget"       
[5] "Gross"         "Date"          "released_year"
print(summary(df[, numeric_cols]))
    Runtime        imdbRating      imdbVotes           Budget         
 Min.   :  1.0   Min.   :1.600   Min.   :      5   Min.   :     1100  
 1st Qu.: 93.0   1st Qu.:5.700   1st Qu.:   8962   1st Qu.:  5250000  
 Median :102.0   Median :6.400   Median :  36268   Median : 20000000  
 Mean   :104.9   Mean   :6.294   Mean   :  84748   Mean   : 34625509  
 3rd Qu.:115.0   3rd Qu.:7.100   3rd Qu.: 101076   3rd Qu.: 45000000  
 Max.   :219.0   Max.   :9.000   Max.   :1670736   Max.   :425000000  
 NA's   :21      NA's   :22      NA's   :22                           
     Gross                Date      released_year 
 Min.   :0.000e+00   Min.   :1999   Min.   :2000  
 1st Qu.:2.924e+06   1st Qu.:2004   1st Qu.:2004  
 Median :3.036e+07   Median :2008   Median :2008  
 Mean   :9.460e+07   Mean   :2008   Mean   :2008  
 3rd Qu.:1.021e+08   3rd Qu.:2012   3rd Qu.:2012  
 Max.   :2.784e+09   Max.   :2016   Max.   :2016  
                                                  
# Number of times to repeat training for stability
num_repeat = 10
model1_cols = c('released_year', 'Runtime', 'imdbRating', 'imdbVotes', 'Budget')
model1_exp = paste('Gross~', paste(model1_cols, collapse='+'))

eval_model1 = eval_model(train_df, test_df, model1_exp, train_sizes, num_repeat=10)
df_eval = eval_model1$df_eval

Q: List the numeric variables you used.

A: The numeric variables that we used were Runtime, imdbRating, imdbVotes, Budget, Gross, Date, released_year. (Recall that we stripped out the tomato-like columns in our preprocessing steps above.)

Q: What is the best mean test RMSE value you observed, and at what training set size?

A:

# Plotting Training and Test RMSE
ggplot(data=df_eval, aes(x=train_size, y=rmses)) +
  geom_line(aes(color=rmse_type, group=rmse_type)) +
  ggtitle('Model 1: Training and Test RMSE')

# Best test RMSE
best_testrmse_mod1 = eval_model1$best_test_rmse
best_train_size_mod1 = eval_model1$best_train_size

The best mean test RMSE value I observed was 8.745068210^{7} and I observed that at the training set size 80 percent. Notice from the graph above as well that we see that Test RMSE generally decreases with higher training set. This makes sense because with very low training set size, the model will overfit the more limited data.

2. Feature transformations

Try to improve the prediction quality from Task 1 as much as possible by adding feature transformations of the numeric variables. Explore both numeric transformations such as power transforms and non-numeric transformations of the numeric variables like binning (e.g. is_budget_greater_than_3M).

# Helper function to plot relative proportions
plot_relative_proportions = function(df, cols){
  # Keyword Args:
  #  df: DataFrame with columns you want to plot relative proportions of
  #  cols: The columns you want to plot relative proportions of
  # Returns:
  #  plot_proportions: ggplot of proportions
  #  top_cols: Vector of Column Counts
  df.subset = df[c('Title', cols)]
  df.long = melt(df.subset, id.vars=c('Title'))
  
  # Removing rows where value is 0 because when value is 0, that means movie is not that genre.
  df.long = df.long[apply(df.long['value'], 1, function(z) !any(z==0)), ]

  # Finally plot relative proportions
  # https://sebastiansauer.github.io/percentage_plot_ggplot2_V2/
  plot_proportions = ggplot(df.long, aes(x=variable)) +
    geom_bar(aes(y = (..count..)/sum(..count..))) +
    ylab('Relative Proportion')
  
  top_cols = sort(colSums(df[cols]), decreasing=TRUE)
  return(list('plot_proportions'=plot_proportions,
              'top_cols'=top_cols))  
}
# From Lesson 8: Preprocessing Data (22. Skewness and Power Transformations)
# Also from Lessong 10: Logistic Regression (19. Increasing Data Dimensionality)
# The Transforms we are going to use are
# 1. x^2
# 2. log(x) (in theory we could choose difference values of lambda to choose different types of power transformations. In this case for simplicity, we will stick with log transformations (e.g. lambda = 0))

# Helper function to create power transforms
create_power_transforms = function(df, column){
  # Keyword Args:
  #  df: DataFrame with data that you want to add power transformations to
  #  column: Column within df that you want to do power transformations to
  # Returns:
  #  df: DataFrame with new columns as transformations
  
  # Power Transforms
  # For now, let's only consider power transformations where lambda = 2 or -1. In theory
  # we could try infinite values of lambda with some kind of cross validation approach to tune this
  # but for simplicity let's just use inverse and quadratic.
  df[[paste0(column, '2')]] = (df[[column]] ^ 2 - 1) / 2  # lambda = 2
  df[[paste0(column, 'neg1')]] = -1 * (df[[column]] ^ (-1) - 1) / (-1)  # lambda = -1
  df[[paste0('log', column)]] = log(df[[column]])
  return(df)
}

create_binning_transforms = function(x, threshold){
  # Keyword Args:
  #  x: Vector of data you want to transfor
  #  threshold: Threshold over which transformed vector equals 1, else 0
  # Returns:
  #  binned: New binned transformed vector
  
  # Power Transforms
  binned = as.numeric(x >= threshold)
  return(binned)
}
# TODO: Build & evaluate model 2 (transformed numeric variables only)

# Creating new variables
df$is_budget_greater_than_60m = as.numeric(df$Budget >= 60000000)
df$imdbRating_greater_than_75 = as.numeric(df$imdbRating >= 7.5)
df = create_power_transforms(df, 'Budget')
df = create_power_transforms(df, 'Runtime')
df = create_power_transforms(df, 'imdbRating')
df = create_power_transforms(df, 'imdbVotes')
df = create_power_transforms(df, 'released_year')

# Adding bins by years
df$early_2000s = as.numeric(df$released_year <= 2004)
df$mid_2000s = as.numeric(df$released_year >= 2005 & df$released_year <= 2009)
df$post_2010 = as.numeric(df$released_year >= 2010)

# Adding bins by Runtime
df$RuntimeUnder75 = as.numeric(df$Runtime <= 75)
df$RuntimeBetween75_125 = as.numeric(df$Runtime > 75 & df$Runtime <= 125)
df$RuntimeGreater125 = as.numeric(df$Runtime > 125)

# "Recreate" the training and test df with the same indices to include the new columns
train_df <- df[train_idx, ]
test_df  <- df[-train_idx, ]

model2_cols = c(model1_cols, c('imdbRating_greater_than_75',
                               'imdbRating2',
                               'is_budget_greater_than_60m',
                               'RuntimeUnder75',
                               'RuntimeBetween75_125',
                               'RuntimeGreater125',
                               'Runtime2',
                               'early_2000s',
                               'mid_2000s',
                               'post_2010'
                               ))
model2_exp = paste('Gross~', paste(model2_cols, collapse='+'))

eval_model2 = eval_model(train_df, test_df, model2_exp, train_sizes, num_repeat=10)
df_eval_mod2 = eval_model2$df_eval

Q: Explain which transformations you used and why you chose them.

A: In addition to the features from Part 1, I created the following new features

  1. Binned imdbRating: Created an indicator variable for if the imdbRating was greater than 7.5
  2. Power Transformation for imdbRating: lambda=2
  3. Binned Budget: Created an indicator variable for if budget greater than 60 million
  4. Binned Runtime (under 75, between 75 and 125, and over 125)
  5. Power Transformation for Runtime: lambda=2
  6. Binned Released Year (between 2000 and 2004; 2005 and 2009; 2010 and after)

First, I created a binned variable for imdbRating at 7.5. (Note, later on in part 5 as we explore more interesting relationships, I interact this binned variable with imdbRating to capture a different relationship after 7.5 – more on this below).

I chose to look at imdbRating because I noticed an interesting relationship with imdbRating and Gross. If you look at the plot below, we plot the average Gross given imdbRating, and notice that the relationship seems to be linear until about 7.5. Then the average gross increases a lot. This is an interesting relationship because maybe movies below 7.5 rating are mostly not that great and so the relationship is linear. But if a movie is really really good, when the relationship between Gross and rating changes, that is a really really good movie grosses much much more. Thus, I chose to create a binned variable at imdbRating = 7.5. This will allow for the model to potentially capture a difference in the effect of rating on Gross for movies with ratings greater than 7.5. However, from the graph, notice that the slope of the relationship is not constant before and after 7.5. Thus, in part 5, we will also create an interaction variable to try to capture a different relationship after imdbRating = 7.5 with imdbRating2 where imdbRating2 is the squared term of imdbRating (since the relationship after imdbRating=7.5 looks exponential. Furthermore, looking at the GGPairs graph, it looks like imdbRating2 has a higher linear correlation with Gross than imdbRating. The other power transformations did not seem to increase linear correlation.)

ggplot(df, aes(imdbRating, Gross)) +
  stat_summary(fun.y = "mean", colour = "red", size = 2, geom = "point") +
  ggtitle('Relationship between Gross and imdbRating')

ggpairs(df[c('imdbRating', 'imdbRating2', 'imdbRatingneg1', 'logimdbRating', 'Gross')])

Next, I looked at the relationship between Gross and imdbVotes. From the graphs below, it doesn’t look like there are meaningful bins that would add value. Furthermore, looking at the GGPairs plot, none of the power transformations add higher linear correlation with Gross, so it does not make sense to add additional transformations with imdbVotes into our model.

ggplot(df, aes(imdbVotes, Gross)) +
  stat_summary(fun.y = "mean", colour = "red", size = 2, geom = "point") +
  ggtitle('Relationship between Gross and imdbVotes')

ggpairs(df[c('imdbVotes', 'imdbVotes2', 'imdbVotesneg1', 'logimdbVotes', 'Gross')])

Next, I look at the relationship between Budget and Gross. This is because if we remember back to Project 1, higher budget films tended to gross much higher. Looking at the graphs below, it seems like $60M would be an interesting bin. However, we also plot the GGPairs plot of Budget2 (power transformation with lambda = 2), Budgetneg1 (power transformation with lambda = -1), logBudget (power transformation with lambda = 0) and Budget against Gross and you can see at least with the linear correlation, none of the power transformations offer better linear correlation with Gross than Budget. Thus, we choose not to add any of the power transformations of Budget into this model.

# Distribution of Gross by Budget
# Now we need to show distribution of runtime by Budget. Do a boxplot grouped by budget
df$budget_rounded = round_any(df$Budget, 10000000, f = floor)
df$budget_rounded[df$Budget >= 60000000] = 60000000
df$budget_rounded = as.character(df$budget_rounded)
df$budget_rounded[df$budget_rounded == '600000000'] = 'Over 60M'

ggplot(df, aes(as.factor(budget_rounded), Gross)) +
  geom_boxplot() +
  coord_flip() +
  scale_x_discrete("Budget Rounded") +
  ggtitle('Distribution of Gross by Budget')

ggplot(df, aes(Budget, Gross)) +
  stat_summary(fun.y = "mean", colour = "red", size = 2, geom = "point")

ggpairs(df[c('Budget', 'Budget2', 'Budgetneg1', 'logBudget', 'Gross')])

Next, we look at the relationship between Runtime and Gross. There are a few things that are interesting here. Notice first that if we look at the relationship between Gross and Runtime there seem to be a few interesting “cut points”. For Runtime between 0 and 75 (up until the orange dotted line), the relationship seems to be flat. For Runtime between 75 and 125, the relationship is linearly positively sloped. Finally, for Runtime more than 125 (over 2 hours), the relationship with Gross seems to be very highly variable. Furthermore, the overall relationship seems to be slightly quadratic; notice that in the GGPairs plot, the correlation between Runtime2 (power transformation with lambda = 2) and Gross is higher than the correlation between Runtime and Gross. Thus, we choose to add bins for Runtime up to 75, between 75 and 125, and over 125. Furthermore, we choose to add power transformation of Runtime2. In Question 5, we will also explore adding interactions between the binned variable and the power transformation, which seems appropriate since the relationship between Runtime and Gross does not seem to be the same within these points.

ggplot(df, aes(Runtime, Gross)) +
  stat_summary(fun.y = "mean", colour = "red", size = 2, geom = "point") +
  geom_vline(xintercept=75, color='orange', linetype='longdash') +
  geom_vline(xintercept=125, color='blue', linetype='longdash') +
  ggtitle('Average Gross vs Runtime')

ggpairs(df[c('Runtime', 'Runtime2', 'Runtimeneg1', 'logRuntime', 'Gross')])

Finally, the last numerical column is released_year. If we plot the average gross amount by released_year we see there are actually “cycles” that appear to be forming.

ggplot(df, aes(released_year, Gross)) +
  geom_point(stat='summary') +
  geom_vline(xintercept=2004.5, color='orange', linetype='longdash') +
  geom_vline(xintercept=2009.5, color='blue', linetype='longdash') +
  ggtitle('Relationship between Gross and Released Year')
No summary function supplied, defaulting to `mean_se()

ggpairs(df[c('released_year', 'released_year2', 'released_yearneg1', 'logreleased_year', 'Gross')])

For example, in the graph above, we see that gross amounts are generally increasing between 2000 to 2004. Then drops in 2005 and generally rises again between 2005 and 2009. Then, average gross amounts drops again in 2010. This is likely reflective of the economy/business cycles. Especially the drop between 2009 to 2010 could be a reflection of the Great Recession where people spent less money on entertainment. Thus, I wanted to, instead of just using released_year as a continuous variable, create three bins of years: early 2000’s, mid 2000’s, and post 2010.

However, notice that in the GGPairs plot, none of the power transformations seemed to offer any additional linear correlation; thus I chose not to add any power transformations of released_year into the model.

Q: How did the RMSE change compared to Task 1?

A:

Below, we plot the Training and Test RMSE for Model 2. Furthermore, we can directly compare the best test RMSE below:

ggplot(data=df_eval_mod2, aes(x=train_size, y=rmses)) +
  geom_line(aes(color=rmse_type, group=rmse_type)) +
  ggtitle('Model 2: Training and Test RMSE')

print(eval_model1$best_test_rmse)
[1] 87450682
print(eval_model2$best_test_rmse)
[1] 86458175
improvement_in_rmse_model2 = eval_model2$best_test_rmse - eval_model1$best_test_rmse
percentage_improvement_in_rmse_model2 = 1.0 * abs(eval_model2$best_test_rmse - eval_model1$best_test_rmse) / eval_model1$best_test_rmse
print('Improvement in RMSE')
[1] "Improvement in RMSE"
print(improvement_in_rmse_model2)
[1] -992507.5
# As a percentage
print(percentage_improvement_in_rmse_model2)
[1] 0.01134934

Notice that the improvement in model2 is -9.925075210^{5} which actually represents a 1.134934 percentage gain! While this might seem modest; remember that we did note some meaningful interactions that will be further explored in Part 5!

Also, we can look at where the best test MSE occured, as a function of training size:

# Best test RMSE
best_testrmse_mod2 = eval_model2$best_test_rmse
best_train_size_mod2 = eval_model2$best_train_size

The best mean test RMSE value I observed was 8.645817510^{7} and I observed that at the training set size 100 percent. Notice from the graph above as well that we see that Test RMSE generally decreases with higher training set. This makes sense because with very low training set size, the model will overfit the more limited data.

3. Non-numeric variables

Write code that converts genre, actors, directors, and other categorical variables to columns that can be used for regression (e.g. binary columns as you did in Project 1). Also process variables such as awards into more useful columns (again, like you did in Project 1). Now use these converted columns only to build your next model.

# Create a helper function to help one-hot encode certain text columns
# For example, Genre, Actors, Directors
# By default we will keep the top ten of each
create_one_hot_encoded = function(df, column, numTopCols=10){
  # Keyword Args:
  #  df: DataFrame with data and column you want to one-hot-encode
  #  column: Column within the df that you want to one-hot encode
  # Returns:
  #  df: Original DataFrame with newly appended columns of numTopCols highest occuring value of Column
  #  top_cols: The top_cols that were returned
  
  # Example of how to one-hot encode: https://stackoverflow.com/questions/39778387/r-dataframe-one-hot-encoding-of-column-containing-multiple-terms
  df = cbind(df, mtabulate(strsplit(df[[column]], ",")))
  cols = unique(unlist(strsplit(df[[column]], ',')))
  top_cols = sort(colSums(df[cols]), decreasing=TRUE)
  top_cols_names = names(top_cols[1:numTopCols])
  non_top_cols_names = setdiff(cols, top_cols_names)
  
  # Remove the non_top_cols
  df[non_top_cols_names] = list(NULL)
  return(list('df'=df,
              'top_cols'=top_cols_names)) 
}
# Processing Genre Column -- Work taken from PR1
# Note: Change 'Sci-Fi' to 'SciFi' because dashes in column names are really annoying in R
# Also changing N/A in Genre to na since / are also really annoying
df$Genre <- gsub('Sci-Fi', 'SciFi', df$Genre)

# Processing Rating Column -- Work taken from PR1
df$rated_cleaned = ifelse(df$Rated %in% c('G', 'PG-13', 'PG', 'R', 'NC-17'), df$Rated, 'Other')
df$rated_cleaned <- gsub('PG-13', 'PG13', df$rated_cleaned)
df$rated_cleaned <- gsub('NC-17', 'NC17', df$rated_cleaned)

# Preprocessing Categorical Variables to remove N/A
df$Actors <- gsub('N/A', 'na', df$Actors)
df$Country <- gsub('N/A', 'na', df$Country)
df$Director <- gsub('N/A', 'na', df$Director)
df$Genre <- gsub('N/A', 'na', df$Genre)
df$Writer <- gsub('N/A', 'na', df$Writer)
df$rated_cleaned <- gsub('N/A', 'na', df$rated_cleaned)

# Preprocessing Categorical Variables to remove spaces
df$Actors <- gsub(' ', '', df$Actors)
df$Country <- gsub(' ', '', df$Country)
df$Director <- gsub(' ', '', df$Director)
df$Genre <- gsub(' ', '', df$Genre)
df$Writer <- gsub(' ', '', df$Writer)
df$rated_cleaned <- gsub(' ', '', df$rated_cleaned)

# Preprocessing to remove parantheses
df$Writer <- gsub('\\(', '', df$Writer)
df$Writer <- gsub('\\)', '', df$Writer)

# Creating one-hot encoded Categorical columns
genre_one_hot_encoded = create_one_hot_encoded(df, 'Genre', 10)
df = genre_one_hot_encoded$df
top_genres = genre_one_hot_encoded$top_cols

actor_one_hot_encoded = create_one_hot_encoded(df, 'Actors', 20)
df = actor_one_hot_encoded$df
top_actors = actor_one_hot_encoded$top_cols

director_one_hot_encoded = create_one_hot_encoded(df, 'Director', 20)
df = director_one_hot_encoded$df
top_directors = director_one_hot_encoded$top_cols

country_one_hot_encoded = create_one_hot_encoded(df, 'Country', 10)
df = country_one_hot_encoded$df
top_countries = country_one_hot_encoded$top_cols

writer_one_hot_encoded = create_one_hot_encoded(df, 'Writer', 20)
df = writer_one_hot_encoded$df
top_writers = writer_one_hot_encoded$top_cols

rating_one_hot_encoded = create_one_hot_encoded(df, 'rated_cleaned', 5)
df = rating_one_hot_encoded$df
top_ratings = rating_one_hot_encoded$top_cols

# Remove columns from DataFrame
df$Genre = NULL
df$Director = NULL
df$Actors = NULL
df$Country = NULL
df$Rating = NULL
# Processing Awards column
# Work taken from PR1
cols = c('Title', 'Awards', 'Gross')
df6 = df[cols]

awards = stri_extract_all(df6$Awards, regex="\\d+")
award_labels = stri_extract_all(tolower(df6$Awards), regex="win|won|wins|nomin", ignore.case=TRUE)

wins_list = c()
nominations_list = c()

for (i in 1:length(awards)){
    wins = 0
    nominations = 0
    for(j in 1:length(awards[[i]])){
        if(!is.na(award_labels[[i]][j]) & (award_labels[[i]][j] == 'wins' | award_labels[[i]][j] == 'win' | award_labels[[i]][j] == 'won')){
            wins = wins + as.numeric(awards[[i]][j])
        } else if (!is.na(award_labels[[i]][j]) & award_labels[[i]][j] == 'nomin'){
            nominations = nominations + as.numeric(awards[[i]][j])
        }
    }
    wins_list = c(wins_list, wins)
    nominations_list = c(nominations_list, nominations)
}

df$wins = wins_list
df$nominations = nominations_list
# TODO: Build & evaluate model 3 (converted non-numeric variables only)
# "Recreate" the training and test df with the same indices to include the new columns
train_df <- df[train_idx, ]
test_df  <- df[-train_idx, ]

# Turning a vector of columns in a model expression: https://stackoverflow.com/questions/7986805/how-do-i-run-a-multiple-linear-regression-using-a-vector-as-my-predictors
model3_cols = c(top_genres, top_directors, top_actors, top_countries, top_writers, top_ratings, c('wins', 'nominations'))
model3_exp = paste('Gross~', paste(model3_cols, collapse='+'))

eval_model3 = eval_model(train_df, test_df, model3_exp, train_sizes, num_repeat=10)
df_eval_mod3 = eval_model3$df_eval

Q: Explain which categorical variables you used, and how you encoded them into features.

A:

The categorical variables that I used were

  1. Genres (top 10)
  2. Directors (top 20)
  3. Actors (top 20)
  4. Countries (top 10)
  5. Writers (top 20)
  6. Awards (processed into wins and nominations)
  7. Ratings (e.g. R, PG13, PG, G; top 5)

The reason for these categorical variables why I only use the top 10/20 is because of curse of dimensionality. If I included all as features, I would have too much variation (ala bias-variance tradeoff). To encode them into features, for each entity (e.g. genre, director, actor) I created a new one-hot encoded binary variable. Each variable would equal 1 if the movie contained that entity, else 0. Please see the helper function I defined create_one_hot_encoded for the code that I used.

Q: What is the best mean test RMSE value you observed, and at what training set size? How does this compare with Task 2?

A:

# Best test RMSE
best_testrmse_mod3 = eval_model3$best_test_rmse
best_train_size_mod3 = eval_model3$best_train_size

The best mean test RMSE value I observed was 1.277982410^{8} and I observed that at the training set size 100 percent. Notice from the graph above as well for model 3 that we see that Test RMSE generally decreases with higher training set. Again, this makes sense because with very low training set size, the model will overfit the more limited data.

We can also compare the best test RMSE directly between Models 1, 2, and 3

ggplot(data=df_eval_mod3, aes(x=train_size, y=rmses)) +
  geom_line(aes(color=rmse_type, group=rmse_type)) +
  ggtitle('Model 3: Training and Test RMSE')

print(eval_model1$best_test_rmse)
[1] 87450682
print(eval_model2$best_test_rmse)
[1] 86458175
print(eval_model3$best_test_rmse)
[1] 127798239

Notice that compared to Model 2, Model 3 performed much worse. This is because we only used categorical variables for Model 3 and we have a pretty sparse data set just using categorical variables. This also potentially suggests that just using the categorical variables is not sufficient, we need to combine numerical and even interact numerical and categorical variables to come up with an even better model.

4. Numeric and categorical variables

Try to improve the prediction quality as much as possible by using both numeric and non-numeric variables from Tasks 2 & 3.

# TODO: Build & evaluate model 4 (numeric & converted non-numeric variables)
model4_cols = unique(c(model2_cols, model3_cols))
model4_exp = paste('Gross~', paste(model4_cols, collapse='+'))

eval_model4 = eval_model(train_df, test_df, model4_exp, train_sizes, num_repeat=10)
df_eval_mod4 = eval_model4$df_eval

Q: Compare the observed RMSE with Tasks 2 & 3.

A: For Model 4, I simply combined (but no additional interactions) the terms from Task 2 and 3 together. The Training vs Test RMSE for Model 4 is shown above. In addition, we can directly compare the best test RMSEs:

ggplot(data=df_eval_mod4, aes(x=train_size, y=rmses)) +
  geom_line(aes(color=rmse_type, group=rmse_type)) +
  ggtitle('Model 4: Training and Test RMSE')

print(eval_model1$best_test_rmse)
[1] 87450682
print(eval_model2$best_test_rmse)
[1] 86458175
print(eval_model3$best_test_rmse)
[1] 127798239
print(eval_model4$best_test_rmse)
[1] 84986467

The improvement of Model 4 compared to Model 2 was:

improvement_in_rmse_model4 = eval_model4$best_test_rmse - eval_model2$best_test_rmse
percentage_improvement_in_rmse_model4 = 1.0 * abs(eval_model4$best_test_rmse - eval_model2$best_test_rmse) / eval_model2$best_test_rmse
print('Improvement in RMSE')
[1] "Improvement in RMSE"
print(improvement_in_rmse_model4)
[1] -1471708
# As a percentage
print(percentage_improvement_in_rmse_model4)
[1] 0.0170222

Notice that the improvement in Model 4 was -1.471708210^{6} which represents a further 1.7022198 percent improvement over Model 2! While this improvement might seem modest, again, in Part 5, we explore how to improve this even further with meaningful interactions. Furthermore, note that there is a massive improvement over Part 3 where we just had categorical variables.

# Best test RMSE
best_testrmse_mod4 = eval_model4$best_test_rmse
best_train_size_mod4 = eval_model4$best_train_size

The best mean test RMSE value I observed for Model 4 was 8.498646710^{7} and I observed that at the training set size 100 percent. Notice again, we see a similar pattern as other models. From the graph above, notice that for Model 4 that we see that Test RMSE generally decreases with higher training set. Again, this makes sense because with very low training set size, the model will overfit the more limited data.

5. Additional features

Now try creating additional features such as interactions (e.g. is_genre_comedy x is_budget_greater_than_3M) or deeper analysis of complex variables (e.g. text analysis of full-text columns like Plot).

# TODO: Build & evaluate model 5 (numeric, non-numeric and additional features)

# Creating interaction variables between variables, as explored and discussed in Part 2
df$imdbRating_greater_than_75_imdbRating = df$imdbRating_greater_than_75 * df$imdbRating
df$imdbRating_greater_than_75_imdbRating2 = df$imdbRating_greater_than_75 * df$imdbRating2
df$is_budget_greater_than_60m_budget = df$is_budget_greater_than_60m * df$Budget
df$Runtime_RuntimeUnder75 = df$Runtime * df$RuntimeUnder75
df$Runtime_RuntimeBetween75_125 = df$Runtime * df$RuntimeBetween75_125
df$Runtime_RuntimeGreater125 = df$Runtime * df$RuntimeGreater125
df$released_year_early_2000s = df$released_year * df$early_2000s
df$released_year_mid_2000s = df$released_year * df$mid_2000s
df$released_year_post_2010 = df$released_year * df$post_2010

# Creating Family Friendly variable that is G, PG, or PG13. Also adding interactions. 
df$family_friendly = as.numeric(df$G + df$PG + df$PG13 >= 1)
df$family_friendly_wins = df$family_friendly * df$wins
df$family_friendly_nominations = df$family_friendly * df$nominations

# Made to DVD
df$made_to_dvd = as.numeric(!is.na(df$DVD))

# Number of Languages
df$number_of_languages = sapply(strsplit(df[['Language']], ","), length)

# "Recreate" the training and test df with the same indices to include the new columns
train_df <- df[train_idx, ]
test_df  <- df[-train_idx, ]

additional_model5_cols = c('imdbRating_greater_than_75_imdbRating',
                           'imdbRating_greater_than_75_imdbRating2',
                           'is_budget_greater_than_60m_budget',
                           'Runtime_RuntimeUnder75',
                           'Runtime_RuntimeBetween75_125',
                           'Runtime_RuntimeGreater125',
                           'released_year_early_2000s',
                           'released_year_mid_2000s',
                           'released_year_post_2010',
                           'family_friendly',
                           'family_friendly_wins',
                           'family_friendly_nominations',
                           'made_to_dvd',
                           'number_of_languages')
model5_cols = unique(c(model4_cols, additional_model5_cols))
model5_exp = paste('Gross~', paste(model5_cols, collapse='+'))

eval_model5 = eval_model(train_df, test_df, model5_exp, train_sizes, num_repeat=10)
df_eval_mod5 = eval_model5$df_eval

Q: Explain what new features you designed and why you chose them.

A: In addition to the features from the previous parts, I created the following new interaction features. Note that the reasoning behind these interactions were already partly explored in Part 2, and I will elaborate further here

  1. Interaction of imdbRating and imdbRating_greater_than_75
  2. Interaction of imdbRating2 and imdbRating_greater_than_75
  3. Interaction of Budget and is_budget_greater_than_60m
  4. Interaction of Runtime and RuntimeUnder75, RuntimeBetween75_125, and RuntimeGreater125
  5. Interaction of Runtime2 and RuntimeUnder75, RuntimeBetween75_125, and RuntimeGreater125
  6. Interaction of released_year and early_2000s, mid_2000s, and post_2010

Furthermore, I explore other relationships that were entirely previously undiscussed such as

  1. family_friendly: Defined as MPAA rating of G, PG, or PG-13
  2. family_friendly_wins: Interaction of family_friendly and wins
  3. family_friendly_nominations: Interaction of family_friendly and nominations.
  4. made_to_dvd: Indicator variable for if movie was made to DVD, intuition being if made to DVD perhaps it was a successful movie and Grossed more.
  5. number_of_languages: Parsed the Language column and count number of languages movie is made. Intuition being if multiple languages, it is an international release/success and thus Grossed more.

For the first 6 interactions, we explored these a bit in Part 2, but I’ll elaborate furhter here. Recall that in Part 2, I created a binned variable for imdbRating at 7.5. Now, I will interact this binned variable with imdbRating and imdbRating2 to capture a different relationship after 7.5 (more on this below).

If we re-look at the relationship between imdbRating and Gross, we plot the average Gross given imdbRating, and notice that the relationship seems to be linear until about 7.5. Then the average gross increases a lot. This is an interesting relationship because maybe movies below 7.5 rating are mostly not that great and so the relationship is linear. But if a movie is really really good, when the relationship between Gross and rating changes, that is a really really good movie grosses much much more. Thus, I chose to create a binned variable at imdbRating = 7.5 and interacted it with imdbRating and imdbRating2 to try to capture a different relationship after imdbRating = 7.5, where imdbRating2 is the squared term of imdbRating (since the relationship after imdbRating=7.5 looks exponential).

ggplot(df, aes(imdbRating, Gross)) +
  stat_summary(fun.y = "mean", colour = "red", size = 2, geom = "point")
Warning: Removed 22 rows containing non-finite values (stat_summary).

Second, I look at the relationship between Budget and Gross. This is because if we remember back to Project 1, higher budget films tended to gross much higher. Looking at the graphs below, it seems like $60M would be an interesting bin, and I wanted to capture the interaction of the binned variable with Budget to allow for a difference in slope after $60M.

# Distribution of Gross by Budget
# Now we need to show distribution of runtime by Budget. Do a boxplot grouped by budget
df$budget_rounded = round_any(df$Budget, 10000000, f = floor)
df$budget_rounded[df$Budget >= 60000000] = 60000000
df$budget_rounded = as.character(df$budget_rounded)
df$budget_rounded[df$budget_rounded == '600000000'] = 'Over 60M'

ggplot(df, aes(as.factor(budget_rounded), Gross)) +
  geom_boxplot() +
  coord_flip() +
  scale_x_discrete("Budget Rounded") +
  ggtitle('Distribution of Gross by Budget')

ggplot(df, aes(Budget, Gross)) +
  stat_summary(fun.y = "mean", colour = "red", size = 2, geom = "point")

Next, with regards to Runtime recall from Part 2 we saw an interesting relationship between the bins of pre-75, between 75 and 125, and after 125 (reproduced visual below). To capture this interaction, we interacted Runtime and Runtime2 with the binned variables.

ggplot(df, aes(Runtime, Gross)) +
  stat_summary(fun.y = "mean", colour = "red", size = 2, geom = "point") +
  geom_vline(xintercept=75, color='orange', linetype='longdash') +
  geom_vline(xintercept=125, color='blue', linetype='longdash') +
  ggtitle('Average Gross vs Runtime')

Next, with regards to released_year recall from Part 2 we saw an interesting relationship, potentially explained by Economic business cycles. Below, we reproduce the visualization, and to allow for the model to capture this interesting effect, we interact released_year with the binned variables.

ggplot(df, aes(released_year, Gross)) +
  geom_point(stat='summary') +
  geom_vline(xintercept=2004.5, color='orange', linetype='longdash') +
  geom_vline(xintercept=2009.5, color='blue', linetype='longdash') +
  ggtitle('Relationship between Gross and Released Year')
No summary function supplied, defaulting to `mean_se()

Next, I wanted to explore MPAA Ratings more because I noticed a pretty significant difference in Gross by different MPAA Ratings. In particular, we can see from the graph below that:

ggplot(df, aes(rated_cleaned, Gross)) +
  geom_bar(stat='summary')
No summary function supplied, defaulting to `mean_se()

It looks like G, PG, and PG13 rated movies gross much higher on average than other MPAA rated movies. This makes sense as these types of movies are more “family friendly” and can relate to a much bigger audience. Thus, I created a binary variable family_friendly and interacted it with wins and nominations to see if there was any meaningful interactions. If we look at the ggpairs, it looks promising.

# Plotting Correlation Plots of the different Review number Columns:
ggpairs(df[c('family_friendly', 'wins', 'family_friendly_wins', 'Gross')])

ggpairs(df[c('family_friendly', 'nominations', 'family_friendly_nominations', 'Gross')])

Among other things from the ggpairs graph above, you can see that the correlation between Gross vs. wins and nominations is fairly high (approximately 0.3 for each), but interacted with family_friendly, the correlation goes up to almost 0.5! Thus, it seems like there is a meaningful interaction between wins, nominations and family_friendly. This essentially means that for family friendly movies, winning and/or being nominated for an award has functionally different relationship with Gross than non-family friendly movies.

Q: Comment on the final RMSE values you obtained, and what you learned through the course of this project.

A: For Model 5, we can directly compare the best test RMSEs:

ggplot(data=df_eval_mod5, aes(x=train_size, y=rmses)) +
  geom_line(aes(color=rmse_type, group=rmse_type)) +
  ggtitle('Model 5: Training and Test RMSE')

print(eval_model1$best_test_rmse)
[1] 87450682
print(eval_model2$best_test_rmse)
[1] 86458175
print(eval_model3$best_test_rmse)
[1] 127798239
print(eval_model4$best_test_rmse)
[1] 84986467
print(eval_model5$best_test_rmse)
[1] 83328591

The improvement of Model 5 compared to Model 4 was:

improvement_in_rmse_model5 = eval_model5$best_test_rmse - eval_model4$best_test_rmse
percentage_improvement_in_rmse_model5 = 1.0 * abs(eval_model5$best_test_rmse - eval_model4$best_test_rmse) / eval_model4$best_test_rmse
print('Improvement in RMSE')
[1] "Improvement in RMSE"
print(improvement_in_rmse_model5)
[1] -1657875
# As a percentage
print(percentage_improvement_in_rmse_model5)
[1] 0.01950752

Notice that the improvement in Model 5 was -1.471708210^{6} which represents a further 1.9507523 percent improvement over Model 4! In addition, notice that the all-in improvement of Model 5 compared to Model 1 is -4.122091110^{6} which represents a 4.713618 percentage improvement!

# Best test RMSE
best_testrmse_mod5 = eval_model5$best_test_rmse
best_train_size_mod5 = eval_model5$best_train_size

The best mean test RMSE value I observed for Model 5 was 8.332859110^{7} and I observed that at the training set size 100 percent. Notice again, we see a similar pattern as other models. From the graph above, notice that for Model 5 that we see that Test RMSE generally decreases with higher training set. Again, this makes sense because with very low training set size, the model will overfit the more limited data.

This project was immensely useful in getting experience into digging deeper about building models. Even with this relatively limited data set; on the order of 1000s of observations and 100s of features, there is practically infinite ways we can engineer new features and thus do feature selection as to what we want to put into our models.

One thing I definitely learned was that feature engineering is useful. Especially with linear regression, the relationship between an outcome variable and potential features is very rarely truly linear. Exploring which transformations and interactions improve the model is important. However, this is not riskless!!! Because of curse of dimensionality and bias/variance tradeoff, you cannot just put whatever you want into the model. Thus, the hold-out test set is important.

Another thing that I learned is the importance of training/test split, and I found the relationship with size of training set interesting. The test error seemed to generally decrease and generally the lowest test error was found when trained on 100% of training set. This is a key lesson since if you train on small sample size, the model will overfit the small training set (even with a relatively inflexible method like linear regression!) Thus, always be careful of overfitting and never trust the training error.

If I wanted to take this a step forward, I would definitely want to include some kind of validation set/procedure (e.g. K-fold validation). However, given the scope of this project assignment and this Piazza post: https://piazza.com/class/j6gt7ycx6nk145?cid=1123, I did not do so here. However, this would be one tool to help us figure out the best features to include in our model. For example, this would be a good tool to help us figure out what power transformations to use.

LS0tCnRpdGxlOiAnUHJvamVjdCAyOiBNb2RlbGluZyBhbmQgRXZhbHVhdGlvbicKc3VidGl0bGU6ICJDU0U2MjQyIC0gRGF0YSBhbmQgVmlzdWFsIEFuYWx5dGljcyAtIEZhbGwgMjAxN1xuXG5EdWU6IFN1bmRheSwgTm92ZW1iZXIgMjYsIDIwMTcgYXQgMTE6NTkgUE0gVVRDLTEyOjAwIG9uIFQtU3F1YXJlIgpvdXRwdXQ6CiAgaHRtbF9ub3RlYm9vazoKICAgIGNvZGVfZm9sZGluZzogbm9uZQogICAgdGhlbWU6IGRlZmF1bHQKICBodG1sX2RvY3VtZW50OgogICAgY29kZV9mb2xkaW5nOiBub25lCiAgICB0aGVtZTogZGVmYXVsdAogIHBkZl9kb2N1bWVudDogZGVmYXVsdAotLS0KCiMgRGF0YQoKV2Ugd2lsbCB1c2UgdGhlIHNhbWUgZGF0YXNldCBhcyBQcm9qZWN0IDE6IFtgbW92aWVzX21lcmdlZGBdKGh0dHBzOi8vczMuYW1hem9uYXdzLmNvbS9jb250ZW50LnVkYWNpdHktZGF0YS5jb20vY291cnNlcy9ndC1jczYyNDIvcHJvamVjdC9tb3ZpZXNfbWVyZ2VkKS4KCiMgT2JqZWN0aXZlCgpZb3VyIGdvYWwgaW4gdGhpcyBwcm9qZWN0IGlzIHRvIGJ1aWxkIGEgbGluZWFyIHJlZ3Jlc3Npb24gbW9kZWwgdGhhdCBjYW4gcHJlZGljdCB0aGUgYEdyb3NzYCByZXZlbnVlIGVhcm5lZCBieSBhIG1vdmllIGJhc2VkIG9uIG90aGVyIHZhcmlhYmxlcy4gWW91IG1heSB1c2UgUiBwYWNrYWdlcyB0byBmaXQgYW5kIGV2YWx1YXRlIGEgcmVncmVzc2lvbiBtb2RlbCAobm8gbmVlZCB0byBpbXBsZW1lbnQgcmVncmVzc2lvbiB5b3Vyc2VsZikuIFBsZWFzZSBzdGljayB0byBsaW5lYXIgcmVncmVzc2lvbiwgaG93ZXZlci4KCiMgSW5zdHJ1Y3Rpb25zCgpZb3Ugc2hvdWxkIGJlIGZhbWlsaWFyIHdpdGggdXNpbmcgYW4gW1JNYXJrZG93bl0oaHR0cDovL3JtYXJrZG93bi5yc3R1ZGlvLmNvbSkgTm90ZWJvb2sgYnkgbm93LiBSZW1lbWJlciB0aGF0IHlvdSBoYXZlIHRvIG9wZW4gaXQgaW4gUlN0dWRpbywgYW5kIHlvdSBjYW4gcnVuIGNvZGUgY2h1bmtzIGJ5IHByZXNzaW5nICpDbWQrU2hpZnQrRW50ZXIqLgoKUGxlYXNlIGNvbXBsZXRlIHRoZSB0YXNrcyBiZWxvdyBhbmQgc3VibWl0IHRoaXMgUiBNYXJrZG93biBmaWxlIChhcyAqKnByMi5SbWQqKikgY29udGFpbmluZyBhbGwgY29tcGxldGVkIGNvZGUgY2h1bmtzIGFuZCB3cml0dGVuIHJlc3BvbnNlcywgYW5kIGEgUERGIGV4cG9ydCBvZiBpdCAoYXMgKipwcjIucGRmKiopIHdoaWNoIHNob3VsZCBpbmNsdWRlIHRoZSBvdXRwdXRzIGFuZCBwbG90cyBhcyB3ZWxsLgoKX05vdGUgdGhhdCAqKlNldHVwKiogYW5kICoqRGF0YSBQcmVwcm9jZXNzaW5nKiogc3RlcHMgZG8gbm90IGNhcnJ5IGFueSBwb2ludHMsIGhvd2V2ZXIsIHRoZXkgbmVlZCB0byBiZSBjb21wbGV0ZWQgYXMgaW5zdHJ1Y3RlZCBpbiBvcmRlciB0byBnZXQgbWVhbmluZ2Z1bCByZXN1bHRzLl8KCiMgU2V0dXAKClNhbWUgYXMgUHJvamVjdCAxLCBsb2FkIHRoZSBkYXRhc2V0IGludG8gbWVtb3J5OgoKYGBge3J9CnNldHdkKCJ+L2dpdC9HZW9yZ2lhVGVjaC9jc2U2MjQyL3ByMiIpCmxvYWQoJ21vdmllc19tZXJnZWQnKQpgYGAKClRoaXMgY3JlYXRlcyBhbiBvYmplY3Qgb2YgdGhlIHNhbWUgbmFtZSAoYG1vdmllc19tZXJnZWRgKS4gRm9yIGNvbnZlbmllbmNlLCB5b3UgY2FuIGNvcHkgaXQgdG8gYGRmYCBhbmQgc3RhcnQgdXNpbmcgaXQ6CgpgYGB7cn0KZGYgPSBtb3ZpZXNfbWVyZ2VkCmNhdCgiRGF0YXNldCBoYXMiLCBkaW0oZGYpWzFdLCAicm93cyBhbmQiLCBkaW0oZGYpWzJdLCAiY29sdW1ucyIsIGVuZD0iXG4iLCBmaWxlPSIiKQpjb2xuYW1lcyhkZikKYGBgCgojIyBMb2FkIFIgcGFja2FnZXMKCkxvYWQgYW55IFIgcGFja2FnZXMgdGhhdCB5b3Ugd2lsbCBuZWVkIHRvIHVzZS4gWW91IGNhbiBjb21lIGJhY2sgdG8gdGhpcyBjaHVuaywgZWRpdCBpdCBhbmQgcmUtcnVuIHRvIGxvYWQgYW55IGFkZGl0aW9uYWwgcGFja2FnZXMgbGF0ZXIuCgpgYGB7cn0KbGlicmFyeShHR2FsbHkpICAjIFVzZWQgZm9yIGdncGFpcnMKbGlicmFyeShnZ3Bsb3QyKQpsaWJyYXJ5KGx1YnJpZGF0ZSkgICMgVXNlZCB0byBleHRyYWN0IHllYXIgZnJvbSBSZWxlYXNlZApsaWJyYXJ5KHFkYXBUb29scykgICMgVXNlZCBmb3IgbXRhYnVsYXRlICh0byBvbmUtaG90IGVuY29kZSBhIGNvbHVtbikKbGlicmFyeShyZXNoYXBlKSAgIyBVc2VkIGZvciBtZWx0IGZ1bmN0aW9uCmxpYnJhcnkoc3RyaW5naSkgICMgVXNlZCBmb3IgcHJvY2Vzc2luZyBBd2FyZHMKYGBgCgpJZiB5b3UgYXJlIHVzaW5nIGFueSBub24tc3RhbmRhcmQgcGFja2FnZXMgKG9uZXMgdGhhdCBoYXZlIG5vdCBiZWVuIGRpc2N1c3NlZCBpbiBjbGFzcyBvciBleHBsaWNpdGx5IGFsbG93ZWQgZm9yIHRoaXMgcHJvamVjdCksIHBsZWFzZSBtZW50aW9uIHRoZW0gYmVsb3cuIEluY2x1ZGUgYW55IHNwZWNpYWwgaW5zdHJ1Y3Rpb25zIGlmIHRoZXkgY2Fubm90IGJlIGluc3RhbGxlZCB1c2luZyB0aGUgcmVndWxhciBgaW5zdGFsbC5wYWNrYWdlcygnPHBrZyBuYW1lPicpYCBjb21tYW5kLgoKKipOb24tc3RhbmRhcmQgcGFja2FnZXMgdXNlZCoqOiBOb25lCgojIERhdGEgUHJlcHJvY2Vzc2luZwoKQmVmb3JlIHdlIHN0YXJ0IGJ1aWxkaW5nIG1vZGVscywgd2Ugc2hvdWxkIGNsZWFuIHVwIHRoZSBkYXRhc2V0IGFuZCBwZXJmb3JtIGFueSBwcmVwcm9jZXNzaW5nIHN0ZXBzIHRoYXQgbWF5IGJlIG5lY2Vzc2FyeS4gU29tZSBvZiB0aGVzZSBzdGVwcyBjYW4gYmUgY29waWVkIGluIGZyb20geW91ciBQcm9qZWN0IDEgc29sdXRpb24uIEl0IG1heSBiZSBoZWxwZnVsIHRvIHByaW50IHRoZSBkaW1lbnNpb25zIG9mIHRoZSByZXN1bHRpbmcgZGF0YWZyYW1lIGF0IGVhY2ggc3RlcC4KCiMjIDEuIFJlbW92ZSBub24tbW92aWUgcm93cwoKYGBge3J9CiMgVE9ETzogUmVtb3ZlIGFsbCByb3dzIGZyb20gZGYgdGhhdCBkbyBub3QgY29ycmVzcG9uZCB0byBtb3ZpZXMKZGYgPC0gZGZbZGYkVHlwZSA9PSAibW92aWUiLF0KYGBgCgojIyAyLiBEcm9wIHJvd3Mgd2l0aCBtaXNzaW5nIGBHcm9zc2AgdmFsdWUKClNpbmNlIG91ciBnb2FsIGlzIHRvIG1vZGVsIGBHcm9zc2AgcmV2ZW51ZSBhZ2FpbnN0IG90aGVyIHZhcmlhYmxlcywgcm93cyB0aGF0IGhhdmUgbWlzc2luZyBgR3Jvc3NgIHZhbHVlcyBhcmUgbm90IHVzZWZ1bCB0byB1cy4KCmBgYHtyfQojIFRPRE86IFJlbW92ZSByb3dzIHdpdGggbWlzc2luZyBHcm9zcyB2YWx1ZQpkZiA9IGRmW3doaWNoKCFpcy5uYShkZiRHcm9zcykpLCBdCmBgYAoKIyMgMy4gRXhjbHVkZSBtb3ZpZXMgcmVsZWFzZWQgcHJpb3IgdG8gMjAwMAoKSW5mbGF0aW9uIGFuZCBvdGhlciBnbG9iYWwgZmluYW5jaWFsIGZhY3RvcnMgbWF5IGFmZmVjdCB0aGUgcmV2ZW51ZSBlYXJuZWQgYnkgbW92aWVzIGR1cmluZyBjZXJ0YWluIHBlcmlvZHMgb2YgdGltZS4gVGFraW5nIHRoYXQgaW50byBhY2NvdW50IGlzIG91dCBvZiBzY29wZSBmb3IgdGhpcyBwcm9qZWN0LCBzbyBsZXQncyBleGNsdWRlIGFsbCBtb3ZpZXMgdGhhdCB3ZXJlIHJlbGVhc2VkIHByaW9yIHRvIHRoZSB5ZWFyIDIwMDAgKHlvdSBtYXkgdXNlIGBSZWxlYXNlZGAsIGBEYXRlYCBvciBgWWVhcmAgZm9yIHRoaXMgcHVycG9zZSkuCgpgYGB7cn0KIyBUT0RPOiBFeGNsdWRlIG1vdmllcyByZWxlYXNlZCBwcmlvciB0byAyMDAwCmRmJHJlbGVhc2VkX3llYXIgPSB5ZWFyKGRmJFJlbGVhc2VkKQpkZiA9IGRmW3doaWNoKGRmJHJlbGVhc2VkX3llYXIgPj0gMjAwMCksIF0KYGBgCgojIyA0LiBFbGltaW5hdGUgbWlzbWF0Y2hlZCByb3dzCgpfTm90ZTogWW91IG1heSBjb21wYXJlIHRoZSBgUmVsZWFzZWRgIGNvbHVtbiAoc3RyaW5nIHJlcHJlc2VudGF0aW9uIG9mIHJlbGVhc2UgZGF0ZSkgd2l0aCBlaXRoZXIgYFllYXJgIG9yIGBEYXRlYCAobnVtZXJpYyByZXByZXNlbnRhdGlvbiBvZiB0aGUgeWVhcikgdG8gZmluZCBtaXNtYXRjaGVzLiBUaGUgZ29hbCBpcyB0byBhdm9pZCByZW1vdmluZyBtb3JlIHRoYW4gMTAlIG9mIHRoZSByb3dzLl8KCmBgYHtyfQojIFRPRE86IFJlbW92ZSBtaXNtYXRjaGVkIHJvd3MKIyBUaGlzIGlzIGp1c3QgdGFrZW4gZnJvbSBteSB3b3JrIGluIHByMTsgb25seSBkcm9wIGlmIHRoZSBkaWZmZXJlbmNlIGluIHRoZSB5ZWFycyBpcyAyIG9yIGdyZWF0ZXIuCmRmJG5vdF9tYXRjaGVkID0gKGFicyhkZiRZZWFyICE9IGRmJHJlbGVhc2VkX3llYXIpID49IDIpCgojIE51bWJlciBvZiByb3dzIHdpdGggbm9uLW51bGwgR3Jvc3MgVmFsdWUKcHJpbnQobnJvdyhkZikpCnByaW50KG5yb3coZGZbd2hpY2goIWlzLm5hKGRmJEdyb3NzKSksIF0pKQoKIyBSZW1vdmUgbWlzbWF0Y2hlZCByb3dzCmRmID0gZGZbd2hpY2goIShkZiRub3RfbWF0Y2hlZCA9PSBUUlVFKSksIF0KcHJpbnQobnJvdyhkZikpCmBgYAoKIyMgNS4gRHJvcCBgRG9tZXN0aWNfR3Jvc3NgIGNvbHVtbgoKYERvbWVzdGljX0dyb3NzYCBpcyBiYXNpY2FsbHkgdGhlIGFtb3VudCBvZiByZXZlbnVlIGEgbW92aWUgZWFybmVkIHdpdGhpbiB0aGUgVVMuIFVuZGVyc3RhbmRhYmx5LCBpdCBpcyB2ZXJ5IGhpZ2hseSBjb3JyZWxhdGVkIHdpdGggYEdyb3NzYCBhbmQgaXMgaW4gZmFjdCBlcXVhbCB0byBpdCBmb3IgbW92aWVzIHRoYXQgd2VyZSBub3QgcmVsZWFzZWQgZ2xvYmFsbHkuIEhlbmNlLCBpdCBzaG91bGQgYmUgcmVtb3ZlZCBmb3IgbW9kZWxpbmcgcHVycG9zZXMuCgpgYGB7cn0KIyBUT0RPOiBFeGNsdWRlIHRoZSBgRG9tZXN0aWNfR3Jvc3NgIGNvbHVtbgpkZiREb21lc3RpY19Hcm9zcyA9IE5VTEwKYGBgCgojIyA2LiBQcm9jZXNzIGBSdW50aW1lYCBjb2x1bW4KCmBgYHtyIHdhcm5pbmc9RkFMU0V9CiMgVE9ETzogUmVwbGFjZSBkZiRSdW50aW1lIHdpdGggYSBudW1lcmljIGNvbHVtbiBjb250YWluaW5nIHRoZSBydW50aW1lIGluIG1pbnV0ZXMKIyBUYWtpbmcgd29yayBmcm9tIHByMQpleHRyYWN0X3J1bnRpbWUgPSBmdW5jdGlvbihyKXsKICAgIHRpbWVzID0gdW5saXN0KHIpCiAgICBtaW51dGVzID0gMAogICAgZm9yIChpIGluIDE6bGVuZ3RoKHRpbWVzKSAtIDEpewogICAgICAgIGlmICh0aW1lc1tpICsgMV0gPT0gJ2gnKXsKICAgICAgICAgICAgbWludXRlcyA9IG1pbnV0ZXMgKyBhcy5udW1lcmljKHRpbWVzW2ldKSAqIDYwCiAgICAgICAgfSBlbHNlIGlmICh0aW1lc1tpICsgMV0gPT0gJ21pbicpewogICAgICAgICAgICBtaW51dGVzID0gbWludXRlcyArIGFzLm51bWVyaWModGltZXNbaV0pCiAgICAgICAgfQogICAgfQogICAgaWYgKG1pbnV0ZXMgPT0gMCl7CiAgICAgICAgcmV0dXJuKE5BKQogICAgfSBlbHNlewogICAgICAgIHJldHVybihtaW51dGVzKQogICAgfQp9Cgp5PXN0cnNwbGl0KGRmJFJ1bnRpbWUsJyAnKQpuZXdfcnVudGltZXMgPSB1bmxpc3QobGFwcGx5KHksIGV4dHJhY3RfcnVudGltZSkpCmRmJFJ1bnRpbWUgPSBuZXdfcnVudGltZXMKYGBgCgpQZXJmb3JtIGFueSBhZGRpdGlvbmFsIHByZXByb2Nlc3Npbmcgc3RlcHMgdGhhdCB5b3UgZmluZCBuZWNlc3NhcnksIHN1Y2ggYXMgZGVhbGluZyB3aXRoIG1pc3NpbmcgdmFsdWVzIG9yIGhpZ2hseSBjb3JyZWxhdGVkIGNvbHVtbnMgKGZlZWwgZnJlZSB0byBhZGQgbW9yZSBjb2RlIGNodW5rcywgbWFya2Rvd24gYmxvY2tzIGFuZCBwbG90cyBoZXJlIGFzIG5lY2Vzc2FyeSkuCgpgYGB7ciB3YXJuaW5nPUZBTFNFfQojIFRPRE8ob3B0aW9uYWwpOiBBZGRpdGlvbmFsIHByZXByb2Nlc3NpbmcKCiMgUGxvdHRpbmcgQ29ycmVsYXRpb24gUGxvdHMgb2YgdGhlIGRpZmZlcmVudCBSYXRpbmcgQ29sdW1uczoKZ2dwYWlycyhkZltjKCdpbWRiUmF0aW5nJywgJ3RvbWF0b1JhdGluZycsICd0b21hdG9Vc2VyUmF0aW5nJywgJ3RvbWF0b01ldGVyJyldKQoKIyBQbG90dGluZyBDb3JyZWxhdGlvbiBQbG90cyBvZiB0aGUgZGlmZmVyZW50IFJldmlldyBudW1iZXIgQ29sdW1uczoKZ2dwYWlycyhkZltjKCdpbWRiVm90ZXMnLCAndG9tYXRvUmV2aWV3cycsICd0b21hdG9Vc2VyUmV2aWV3cycpXSkKCiMgUmVtb3ZlIHRvbWF0by1saWtlIENvbHVtbnMgdG8gYXZvaWQgaGlnaCB2YXJpYW5jZSBpbiBtb2RlbHMuCmRmJHRvbWF0b01ldGVyID0gTlVMTApkZiR0b21hdG9SYXRpbmcgPSBOVUxMCmRmJHRvbWF0b1Jldmlld3MgPSBOVUxMCmRmJHRvbWF0b0ZyZXNoID0gTlVMTApkZiR0b21hdG9Sb3R0ZW4gPSBOVUxMCmRmJHRvbWF0b1VzZXJNZXRlciA9IE5VTEwKZGYkdG9tYXRvVXNlclJhdGluZyA9IE5VTEwKZGYkdG9tYXRvVXNlclJldmlld3MgPSBOVUxMCgojIFJlbW92ZSBZZWFyIHNpbmNlIHdlIHdpbGwgdXNlIHJlbGVhc2VkX3llYXIgY29sdW1uCmRmJFllYXIgPSBOVUxMCgojIEFsc28gY3JlYXRpbmcgY2F0ZWdvcmljYWwgdmFsdWUgZm9yIHJ1bnRpbWUsIGp1c3QgYXMgd2UgZGlkIGluIFByb2plY3QgMSB0byBleHBsb3JlIHJlbGF0aW9uc2hpcHMKZGYkcnVudGltZV9jYXRlZ29yaWNhbCA9IGlmZWxzZShkZiRSdW50aW1lIDw9IDY4LCAnc2hvcnQnLCBpZmVsc2UoZGYkUnVudGltZSA8PSAxMDEsICdtZWRpdW0nLCAnbG9uZycpKQpgYGAKCkZyb20gdGhlIGFib3ZlLCBub3RpY2UgdGhhdCBgaW1kYlJhdGluZ2AgaXMgdmVyeSBoaWdobHkgY29ycmVsYXRlZCB3aXRoIGB0b21hdG9SYXRpbmdgLCBgdG9tYXRvVXNlclJhdGluZ2AgYW5kIGB0b21hdG9NZXRlcmAuIFRodXMsIHRvIGF2b2lkIGhpZ2ggdmFyaWFuY2UgaW4gdGhlIG1vZGVscywgaXQgbWFrZXMgbW9yZSBzZW5zZSB0byBqdXN0IGluY2x1ZGUgYGltZGJSYXRpbmdgIGFzIGEgcHJveHkgZm9yIHRoZSByYXRpbmcgb2YgdGhlIG1vdmllLiAKCldoZW4gcGxvdHRpbmcgYSBzaW1pbGFyIHBsb3QgZm9yIHVzZXIgcmV2aWV3cywgaXQgYWdhaW4sIGl0IGxvb2tzIGxpa2UgYGltZGJWb3Rlc2AgaXMgaGlnaGx5IGNvcnJlbGF0ZWQgd2l0aCBgdG9tYXRvUmV2aWV3c2AuIEZ1cnRoZXJtb3JlLCB3aGlsZSBgaW1kYlZvdGVzYCBpcyBub3QgYXMgY29ycmVsYXRlZCB3aXRoIGB0b21hdG9Vc2VyUmV2aWV3c2AsIHRoZXJlIGRvZXMgc2VlbSB0byBiZSBzdGlsbCBzdWZmaWNpZW50IGxpbmVhciBjb3JyZWxhdGlvbi4gQmFzZWQgb24gdGhlc2UgdHdvIHBsb3RzLCBpbiB0aGlzIGFzc2lnbm1lbnQsIHdlJ3JlIGdvaW5nIHRvIHJlbW92ZSBhbGwgdGhlICd0b21hdG8nIGNvbHVtbnMgdG8gYXZvaWQgdW5uZWNlc3NhcnkgcG90ZW50aWFsbHkgaGlnaCB2YXJpYW5jZSBpbiBtb2RlbHMgKGFsYSBiaWFzLXZhcmlhbmNlIHRyYWRlb2ZmKS4gSXQgc2VlbXMgdGhhdCBib3RoIGBpbWRiUmF0aW5nYCBhbmQgYGltZGJWb3Rlc2AgY2FwdHVyZSBlbm91Z2ggaW5mb3JtYXRpb24gYWxvbmcgdGhlc2UgZGltZW5zaW9ucy4KCldlIHdpbGwgYWxzbyByZW1vdmUgbW92aWVzIHJlbGVhc2VkIGluIDIwMTcuIE5vdGljZSB0aGF0IGlmIHdlIGxvb2sgYXQgdGhlIGRpc3RyaWJ1dGlvbiBvZiBtb3ZpZXMgYnkgeWVhcjoKCmBgYHtyfQp0YWJsZShkZiRyZWxlYXNlZF95ZWFyKQpgYGAKCm5vdGljZSB0aGF0IG9ubHkgNCBtb3ZpZXMgd2VyZSByZWxlYXNlZCBpbiAyMDE3IGluIG91ciBkYXRhIHNldC4gVGhpcyBpcyBwcm9iYWJseSBqdXN0IGEgY29uc2VxdWVuY2Ugb2Ygd2hlbiB0aGlzIGRhdGEgd2FzIHB1bGxlZC4gSXQgcHJvYmFibHkgaXMgbm90IHJlcHJlc2VudGF0aXZlIHRvIGluY2x1ZGUgdGhpcyBkYXRhLCBzaW5jZSB0aGlzIGlzIG5vdCBhY3R1YWxseSByZXByZXNlbnRhdGl2ZSBvZiBtb3ZpZXMgaW4gMjAxNy4gVGh1cywgd2Ugd2lsbCByZW1vdmUgdGhlc2UgNCBvYnNlcnZhdGlvbnMKCmBgYHtyfQpkZiA9IGRmW3doaWNoKGRmJHJlbGVhc2VkX3llYXIgPCAyMDE3KSwgXQpgYGAKCl8qKk5vdGUqKjogRG8gTk9UIGNvbnZlcnQgY2F0ZWdvcmljYWwgdmFyaWFibGVzIChsaWtlIGBHZW5yZWApIGludG8gYmluYXJ5IGNvbHVtbnMgeWV0LiBZb3Ugd2lsbCBkbyB0aGF0IGxhdGVyIGFzIHBhcnQgb2YgYSBtb2RlbCBpbXByb3ZlbWVudCB0YXNrLl8KCiMjIEZpbmFsIHByZXByb2Nlc3NlZCBkYXRhc2V0CgpSZXBvcnQgdGhlIGRpbWVuc2lvbnMgb2YgdGhlIHByZXByb2Nlc3NlZCBkYXRhc2V0IHlvdSB3aWxsIGJlIHVzaW5nIGZvciBtb2RlbGluZyBhbmQgZXZhbHVhdGlvbiwgYW5kIHByaW50IGFsbCB0aGUgZmluYWwgY29sdW1uIG5hbWVzLiAoQWdhaW4sIGBEb21lc3RpY19Hcm9zc2Agc2hvdWxkIG5vdCBiZSBpbiB0aGlzIGxpc3QhKQoKYGBge3J9CiMgVE9ETzogUHJpbnQgdGhlIGRpbWVuc2lvbnMgb2YgdGhlIGZpbmFsIHByZXByb2Nlc3NlZCBkYXRhc2V0IGFuZCBjb2x1bW4gbmFtZXMKY2F0KCJEYXRhc2V0IGhhcyIsIGRpbShkZilbMV0sICJyb3dzIGFuZCIsIGRpbShkZilbMl0sICJjb2x1bW5zIiwgZW5kPSJcbiIsIGZpbGU9IiIpCmNvbG5hbWVzKGRmKQpgYGAKCiMgRXZhbHVhdGlvbiBTdHJhdGVneQoKSW4gZWFjaCBvZiB0aGUgdGFza3MgZGVzY3JpYmVkIGluIHRoZSBuZXh0IHNlY3Rpb24sIHlvdSB3aWxsIGJ1aWxkIGEgcmVncmVzc2lvbiBtb2RlbC4gSW4gb3JkZXIgdG8gY29tcGFyZSB0aGVpciBwZXJmb3JtYW5jZSwgeW91IHdpbGwgY29tcHV0ZSB0aGUgdHJhaW5pbmcgYW5kIHRlc3QgUm9vdCBNZWFuIFNxdWFyZWQgRXJyb3IgKFJNU0UpIGF0IGRpZmZlcmVudCB0cmFpbmluZyBzZXQgc2l6ZXMuCgpGaXJzdCwgcmFuZG9tbHkgc2FtcGxlIDEwLTIwJSBvZiB0aGUgcHJlcHJvY2Vzc2VkIGRhdGFzZXQgYW5kIGtlZXAgdGhhdCBhc2lkZSBhcyB0aGUgKip0ZXN0IHNldCoqLiBEbyBub3QgdXNlIHRoZXNlIHJvd3MgZm9yIHRyYWluaW5nISBUaGUgcmVtYWluZGVyIG9mIHRoZSBwcmVwcm9jZXNzZWQgZGF0YXNldCBpcyB5b3VyICoqdHJhaW5pbmcgZGF0YSoqLgoKTm93IHVzZSB0aGUgZm9sbG93aW5nIGV2YWx1YXRpb24gcHJvY2VkdXJlIGZvciBlYWNoIG1vZGVsOgoKLSBDaG9vc2UgYSBzdWl0YWJsZSBzZXF1ZW5jZSBvZiB0cmFpbmluZyBzZXQgc2l6ZXMsIGUuZy4gMTAlLCAyMCUsIDMwJSwgLi4uLCAxMDAlICgxMC0yMCBkaWZmZXJlbnQgc2l6ZXMgc2hvdWxkIHN1ZmZpY2UpLiBGb3IgZWFjaCBzaXplLCBzYW1wbGUgdGhhdCBtYW55IGlucHV0cyBmcm9tIHRoZSB0cmFpbmluZyBkYXRhLCB0cmFpbiB5b3VyIG1vZGVsLCBhbmQgY29tcHV0ZSB0aGUgcmVzdWx0aW5nIHRyYWluaW5nIGFuZCB0ZXN0IFJNU0UuCi0gUmVwZWF0IHlvdXIgdHJhaW5pbmcgYW5kIGV2YWx1YXRpb24gYXQgbGVhc3QgMTAgdGltZXMgYXQgZWFjaCB0cmFpbmluZyBzZXQgc2l6ZSwgYW5kIGF2ZXJhZ2UgdGhlIFJNU0UgcmVzdWx0cyBmb3Igc3RhYmlsaXR5LgotIEdlbmVyYXRlIGEgZ3JhcGggb2YgdGhlIGF2ZXJhZ2VkIHRyYWluIGFuZCB0ZXN0IFJNU0UgdmFsdWVzIGFzIGEgZnVuY3Rpb24gb2YgdGhlIHRyYWluIHNldCBzaXplICglKSwgd2l0aCBvcHRpb25hbCBlcnJvciBiYXJzLgoKWW91IGNhbiBkZWZpbmUgYSBoZWxwZXIgZnVuY3Rpb24gdGhhdCBhcHBsaWVzIHRoaXMgcHJvY2VkdXJlIHRvIGEgZ2l2ZW4gc2V0IG9mIGZlYXR1cmVzIGFuZCByZXVzZSBpdC4KCmBgYHtyfQojIFNldHRpbmcgdXAgVHJhaW5pbmcgYW5kIFRlc3QgREYKc3BsaXRfdHJhaW5fdGVzdF9kZiA9IGZ1bmN0aW9uKGRmLCB0cmFpbl9zcGxpdD0wLjgpewogICMgR2l2ZW4gYSBEYXRhRnJhbWUsIHJhbmRvbWx5IHNwbGl0IHRvIHRyYWluaW5nIGFuZCB0ZXN0IERhdGFGcmFtZQogICMgTm93IFNlbGVjdGluZyA4MCUgb2YgZGF0YSBhcyBzYW1wbGUgZnJvbSB0b3RhbCAnbicgcm93cyBvZiB0aGUgZGF0YQogIHNldC5zZWVkKDEwMCkKICBzYW1wbGUgPC0gc2FtcGxlLmludChuID0gbnJvdyhkZiksIHNpemUgPSBmbG9vciguOCpucm93KGRmKSksIHJlcGxhY2UgPSBGKQogIHRyYWluX2RmIDwtIGRmW3NhbXBsZSwgXQogIHRlc3RfZGYgIDwtIGRmWy1zYW1wbGUsIF0KICByZXR1cm4obGlzdCgndHJhaW5fZGYnPXRyYWluX2RmLAogICAgICAgICAgICAgICd0ZXN0X2RmJz10ZXN0X2RmLAogICAgICAgICAgICAgICd0cmFpbl9pZHgnPXNhbXBsZSkpCn0KCnNwbGl0X2RmID0gc3BsaXRfdHJhaW5fdGVzdF9kZihkZiwgdHJhaW5fc3BsaXQ9MC44KQp0cmFpbl9kZiA9IHNwbGl0X2RmJHRyYWluX2RmCnRlc3RfZGYgPSBzcGxpdF9kZiR0ZXN0X2RmCnRyYWluX2lkeCA9IHNwbGl0X2RmJHRyYWluX2lkeAoKIyBDcmVhdGUgdHJhaW5pbmcgc2V0IHNpemUgdmVjdG9yCnRyYWluX3NpemVzID0gYygwLjEsIDAuMiwgMC4zLCAwLjQsIDAuNSwgMC42LCAwLjcsIDAuOCwgMC45LCAxKQpgYGAKCmBgYHtyfQojIEhlbHBlciBGdW5jdGlvbnMKZ2V0cm1zZSA9IGZ1bmN0aW9uKG1vZCwgdGVzdF9kZil7CiAgICAjIEhlbHBlciBmdW5jdGlvbiB0byByZXR1cm4gYm90aCB0aGUgdHJhaW5pbmcgUk1TRSBhbmQgdGVzdCBSTVNFIGdpdmVuIG1vZGVsIGFuZCB0ZXN0IERhdGFGcmFtZQogICAgIyBGcm9tIGh0dHBzOi8vc3RhY2tvdmVyZmxvdy5jb20vcXVlc3Rpb25zLzQzMTIzNDYyL2hvdy10by1vYnRhaW4tcm1zZS1vdXQtb2YtbG0tcmVzdWx0CiAgICB0cmFpbl9yc3MgPSBjKGNyb3NzcHJvZChtb2QkcmVzaWR1YWxzKSkKICAgIHRyYWluX21zZSA9IHRyYWluX3JzcyAvIGxlbmd0aChtb2QkcmVzaWR1YWxzKQogICAgdHJhaW5fcm1zZSA9IHNxcnQodHJhaW5fbXNlKQogICAgCiAgICB0ZXN0X3Jtc2UgPSBzcXJ0KG1lYW4oKHRlc3RfZGYkR3Jvc3MgLSBwcmVkaWN0LmxtKG1vZCwgdGVzdF9kZikpIF4gMiwgbmEucm09VFJVRSkpCiAgICAKICAgIHJldHVybihsaXN0KCd0cmFpbl9ybXNlJz10cmFpbl9ybXNlLAogICAgICAgICAgICAgICAgJ3Rlc3Rfcm1zZSc9dGVzdF9ybXNlKSkKfQpgYGAKCmBgYHtyfQojIEhlbHBlciBmdW5jdGlvbiB0byBidWlsZCBhbmQgZXZhbHVhdGUgbW9kZWwgYWNjb3JkaW5nIHRvIHRoZSBpbnN0cnVjdGlvbnMgcHJvdmlkZWQKZXZhbF9tb2RlbCA9IGZ1bmN0aW9uKHRyYWluX2RmLCB0ZXN0X2RmLCBtb2RlbF9leHAsIHRyYWluX3NpemVzLCBudW1fcmVwZWF0PTEwKXsKICAjIEtleXdvcmQgQXJnczoKICAjICB0cmFpbl9kZjogVHJhaW5pbmcgRGF0YUZyYW1lCiAgIyAgdGVzdF9kZjogVGVzdCBEYXRhRnJhbWUKICAjICBtb2RlbF9leHA6IEV4cHJlc3Npb24gb2YgbGluZWFyIHJlZ3Jlc3Npb24gbW9kZWwgYXMgYSBzdHJpbmcKICAjICBudW1fcmVwZWF0OiBOdW1iZXIgb2YgdGltZXMgdG8gcmVwZWF0IGV2YWx1YXRpbmcgdGhlIG1vZGVsIChzaW5jZSB0aGVyZSBtaWdodCBiZSBoaWdoIHZhcmlhbmNlKQogICMgUmV0dXJuczogZGZfZXZhbCB3aGljaCBpcyBhIERhdGFGcmFtZSB3aXRoIHRlc3QgYW5kIHRyYWluIHJtc2UKICB0cmFpbl9ybXNlX2FsbCA9IGMoKQogIHRlc3Rfcm1zZV9hbGwgPSBjKCkKICBmb3IodHJhaW5fc2l6ZSBpbiB0cmFpbl9zaXplcyl7CiAgICB0cmFpbl9ybXNlX29uZV9zaXplID0gYygpCiAgICB0ZXN0X3Jtc2Vfb25lX3NpemUgPSBjKCkKICAgIGZvcihyIGluIDE6bnVtX3JlcGVhdCl7CiAgICAgIHNhbXBsZSA8LSBzYW1wbGUuaW50KG4gPSBucm93KHRyYWluX2RmKSwgc2l6ZSA9IGZsb29yKHRyYWluX3NpemUqbnJvdyh0cmFpbl9kZikpLCByZXBsYWNlID0gRikKICAgICAgdHJhaW5fZGZfc3Vic2V0ID0gdHJhaW5fZGZbc2FtcGxlLCBdCiAgICAgIAogICAgICAjIFRyYWluaW5nIGFuZCBldmFsdWF0aW5nIG1vZGVsCiAgICAgIG1vZDEgPSBsbShtb2RlbF9leHAsIGRhdGE9dHJhaW5fZGZfc3Vic2V0KQogICAgICBtb2RlbF9ldmFsID0gZ2V0cm1zZShtb2QxLCB0ZXN0X2RmKQogICAgICB0cmFpbl9ybXNlID0gbW9kZWxfZXZhbCR0cmFpbl9ybXNlCiAgICAgIHRlc3Rfcm1zZSA9IG1vZGVsX2V2YWwkdGVzdF9ybXNlCiAgICAgIAogICAgICB0cmFpbl9ybXNlX29uZV9zaXplID0gYyh0cmFpbl9ybXNlX29uZV9zaXplLCB0cmFpbl9ybXNlKQogICAgICB0ZXN0X3Jtc2Vfb25lX3NpemUgPSBjKHRlc3Rfcm1zZV9vbmVfc2l6ZSwgdGVzdF9ybXNlKQogICAgfQogICAgdHJhaW5fcm1zZV9hbGwgPSBjKHRyYWluX3Jtc2VfYWxsLCBtZWFuKHRyYWluX3Jtc2Vfb25lX3NpemUpKQogICAgdGVzdF9ybXNlX2FsbCA9IGModGVzdF9ybXNlX2FsbCwgbWVhbih0ZXN0X3Jtc2Vfb25lX3NpemUpKQogIH0KICAKICBybXNlX3R5cGUgPSBjKHJlcCgndHJhaW4nLCBsZW5ndGgodHJhaW5fc2l6ZXMpKSwgcmVwKCd0ZXN0JywgbGVuZ3RoKHRyYWluX3NpemVzKSkpCiAgcm1zZXMgPSBjKHRyYWluX3Jtc2VfYWxsLCB0ZXN0X3Jtc2VfYWxsKQogIAogIGRmX2V2YWwgPSBkYXRhLmZyYW1lKHRyYWluX3NpemU9dHJhaW5fc2l6ZXMsIHJtc2VfdHlwZT1ybXNlX3R5cGUsIHJtc2VzPXJtc2VzKQogIGJlc3RfdGVzdF9ybXNlID0gbWluKGRmX2V2YWxbZGZfZXZhbCRybXNlX3R5cGU9PSd0ZXN0JywgJ3Jtc2VzJ10pCiAgYmVzdF90cmFpbl9zaXplID0gdHJhaW5fc2l6ZXNbd2hpY2gubWluKGRmX2V2YWxbZGZfZXZhbCRybXNlX3R5cGU9PSd0ZXN0JywgJ3Jtc2VzJ10pXQoKICByZXR1cm4obGlzdCgnZGZfZXZhbCc9ZGZfZXZhbCwKICAgICAgICAgICAgICAnYmVzdF90ZXN0X3Jtc2UnPWJlc3RfdGVzdF9ybXNlLAogICAgICAgICAgICAgICdiZXN0X3RyYWluX3NpemUnPWJlc3RfdHJhaW5fc2l6ZSkpICAKfQpgYGAKCiMgVGFza3MKCkVhY2ggb2YgdGhlIGZvbGxvd2luZyB0YXNrcyBpcyB3b3J0aCAyMCBwb2ludHMsIGZvciBhIHRvdGFsIG9mIDEwMCBwb2ludHMgZm9yIHRoaXMgcHJvamVjdC4gUmVtZW1iZXIgdG8gYnVpbGQgZWFjaCBtb2RlbCBhcyBzcGVjaWZpZWQsIGV2YWx1YXRlIGl0IHVzaW5nIHRoZSBzdHJhdGVneSBvdXRsaW5lZCBhYm92ZSwgYW5kIHBsb3QgdGhlIHRyYWluaW5nIGFuZCB0ZXN0IGVycm9ycyBieSB0cmFpbmluZyBzZXQgc2l6ZSAoJSkuCgojIyAxLiBOdW1lcmljIHZhcmlhYmxlcwoKVXNlIExpbmVhciBSZWdyZXNzaW9uIHRvIHByZWRpY3QgYEdyb3NzYCBiYXNlZCBvbiBhdmFpbGFibGUgX251bWVyaWNfIHZhcmlhYmxlcy4gWW91IGNhbiBjaG9vc2UgdG8gaW5jbHVkZSBhbGwgb3IgYSBzdWJzZXQgb2YgdGhlbS4KCmBgYHtyfQojIFRPRE86IEJ1aWxkICYgZXZhbHVhdGUgbW9kZWwgMSAobnVtZXJpYyB2YXJpYWJsZXMgb25seSkKbnVtZXJpY19jb2xzID0gY29sbmFtZXMoZGZbLCBzYXBwbHkoZGYsIGlzLm51bWVyaWMpXSkKcHJpbnQobnVtZXJpY19jb2xzKQpwcmludChzdW1tYXJ5KGRmWywgbnVtZXJpY19jb2xzXSkpCgojIE51bWJlciBvZiB0aW1lcyB0byByZXBlYXQgdHJhaW5pbmcgZm9yIHN0YWJpbGl0eQpudW1fcmVwZWF0ID0gMTAKbW9kZWwxX2NvbHMgPSBjKCdyZWxlYXNlZF95ZWFyJywgJ1J1bnRpbWUnLCAnaW1kYlJhdGluZycsICdpbWRiVm90ZXMnLCAnQnVkZ2V0JykKbW9kZWwxX2V4cCA9IHBhc3RlKCdHcm9zc34nLCBwYXN0ZShtb2RlbDFfY29scywgY29sbGFwc2U9JysnKSkKCmV2YWxfbW9kZWwxID0gZXZhbF9tb2RlbCh0cmFpbl9kZiwgdGVzdF9kZiwgbW9kZWwxX2V4cCwgdHJhaW5fc2l6ZXMsIG51bV9yZXBlYXQ9MTApCmRmX2V2YWwgPSBldmFsX21vZGVsMSRkZl9ldmFsCmBgYAoKKipRKio6IExpc3QgdGhlIG51bWVyaWMgdmFyaWFibGVzIHlvdSB1c2VkLgoKKipBKio6IApUaGUgbnVtZXJpYyB2YXJpYWJsZXMgdGhhdCB3ZSB1c2VkIHdlcmUgYHIge251bWVyaWNfY29sc31gLiAoUmVjYWxsIHRoYXQgd2Ugc3RyaXBwZWQgb3V0IHRoZSB0b21hdG8tbGlrZSBjb2x1bW5zIGluIG91ciBwcmVwcm9jZXNzaW5nIHN0ZXBzIGFib3ZlLikKCioqUSoqOiBXaGF0IGlzIHRoZSBiZXN0IG1lYW4gdGVzdCBSTVNFIHZhbHVlIHlvdSBvYnNlcnZlZCwgYW5kIGF0IHdoYXQgdHJhaW5pbmcgc2V0IHNpemU/CgoqKkEqKjogCgpgYGB7cn0KIyBQbG90dGluZyBUcmFpbmluZyBhbmQgVGVzdCBSTVNFCmdncGxvdChkYXRhPWRmX2V2YWwsIGFlcyh4PXRyYWluX3NpemUsIHk9cm1zZXMpKSArCiAgZ2VvbV9saW5lKGFlcyhjb2xvcj1ybXNlX3R5cGUsIGdyb3VwPXJtc2VfdHlwZSkpICsKICBnZ3RpdGxlKCdNb2RlbCAxOiBUcmFpbmluZyBhbmQgVGVzdCBSTVNFJykKCiMgQmVzdCB0ZXN0IFJNU0UKYmVzdF90ZXN0cm1zZV9tb2QxID0gZXZhbF9tb2RlbDEkYmVzdF90ZXN0X3Jtc2UKYmVzdF90cmFpbl9zaXplX21vZDEgPSBldmFsX21vZGVsMSRiZXN0X3RyYWluX3NpemUKYGBgCgpUaGUgYmVzdCBtZWFuIHRlc3QgUk1TRSB2YWx1ZSBJIG9ic2VydmVkIHdhcyBgciB7YmVzdF90ZXN0cm1zZV9tb2QxfWAgYW5kIEkgb2JzZXJ2ZWQgdGhhdCBhdCB0aGUgdHJhaW5pbmcgc2V0IHNpemUgYHIge2Jlc3RfdHJhaW5fc2l6ZV9tb2QxICogMTAwfWAgcGVyY2VudC4gTm90aWNlIGZyb20gdGhlIGdyYXBoIGFib3ZlIGFzIHdlbGwgdGhhdCB3ZSBzZWUgdGhhdCBUZXN0IFJNU0UgZ2VuZXJhbGx5IGRlY3JlYXNlcyB3aXRoIGhpZ2hlciB0cmFpbmluZyBzZXQuIFRoaXMgbWFrZXMgc2Vuc2UgYmVjYXVzZSB3aXRoIHZlcnkgbG93IHRyYWluaW5nIHNldCBzaXplLCB0aGUgbW9kZWwgd2lsbCBvdmVyZml0IHRoZSBtb3JlIGxpbWl0ZWQgZGF0YS4KCiMjIDIuIEZlYXR1cmUgdHJhbnNmb3JtYXRpb25zCgpUcnkgdG8gaW1wcm92ZSB0aGUgcHJlZGljdGlvbiBxdWFsaXR5IGZyb20gKipUYXNrIDEqKiBhcyBtdWNoIGFzIHBvc3NpYmxlIGJ5IGFkZGluZyBmZWF0dXJlIHRyYW5zZm9ybWF0aW9ucyBvZiB0aGUgbnVtZXJpYyB2YXJpYWJsZXMuIEV4cGxvcmUgYm90aCBudW1lcmljIHRyYW5zZm9ybWF0aW9ucyBzdWNoIGFzIHBvd2VyIHRyYW5zZm9ybXMgYW5kIG5vbi1udW1lcmljIHRyYW5zZm9ybWF0aW9ucyBvZiB0aGUgbnVtZXJpYyB2YXJpYWJsZXMgbGlrZSBiaW5uaW5nIChlLmcuIGBpc19idWRnZXRfZ3JlYXRlcl90aGFuXzNNYCkuCgpgYGB7ciBoZWxwZXItcGxvdC1yZWxhdGl2ZS1wcm9wb3J0aW9uc30KIyBIZWxwZXIgZnVuY3Rpb24gdG8gcGxvdCByZWxhdGl2ZSBwcm9wb3J0aW9ucwpwbG90X3JlbGF0aXZlX3Byb3BvcnRpb25zID0gZnVuY3Rpb24oZGYsIGNvbHMpewogICMgS2V5d29yZCBBcmdzOgogICMgIGRmOiBEYXRhRnJhbWUgd2l0aCBjb2x1bW5zIHlvdSB3YW50IHRvIHBsb3QgcmVsYXRpdmUgcHJvcG9ydGlvbnMgb2YKICAjICBjb2xzOiBUaGUgY29sdW1ucyB5b3Ugd2FudCB0byBwbG90IHJlbGF0aXZlIHByb3BvcnRpb25zIG9mCiAgIyBSZXR1cm5zOgogICMgIHBsb3RfcHJvcG9ydGlvbnM6IGdncGxvdCBvZiBwcm9wb3J0aW9ucwogICMgIHRvcF9jb2xzOiBWZWN0b3Igb2YgQ29sdW1uIENvdW50cwogIGRmLnN1YnNldCA9IGRmW2MoJ1RpdGxlJywgY29scyldCiAgZGYubG9uZyA9IG1lbHQoZGYuc3Vic2V0LCBpZC52YXJzPWMoJ1RpdGxlJykpCiAgCiAgIyBSZW1vdmluZyByb3dzIHdoZXJlIHZhbHVlIGlzIDAgYmVjYXVzZSB3aGVuIHZhbHVlIGlzIDAsIHRoYXQgbWVhbnMgbW92aWUgaXMgbm90IHRoYXQgZ2VucmUuCiAgZGYubG9uZyA9IGRmLmxvbmdbYXBwbHkoZGYubG9uZ1sndmFsdWUnXSwgMSwgZnVuY3Rpb24oeikgIWFueSh6PT0wKSksIF0KCiAgIyBGaW5hbGx5IHBsb3QgcmVsYXRpdmUgcHJvcG9ydGlvbnMKICAjIGh0dHBzOi8vc2ViYXN0aWFuc2F1ZXIuZ2l0aHViLmlvL3BlcmNlbnRhZ2VfcGxvdF9nZ3Bsb3QyX1YyLwogIHBsb3RfcHJvcG9ydGlvbnMgPSBnZ3Bsb3QoZGYubG9uZywgYWVzKHg9dmFyaWFibGUpKSArCiAgICBnZW9tX2JhcihhZXMoeSA9ICguLmNvdW50Li4pL3N1bSguLmNvdW50Li4pKSkgKwogICAgeWxhYignUmVsYXRpdmUgUHJvcG9ydGlvbicpCiAgCiAgdG9wX2NvbHMgPSBzb3J0KGNvbFN1bXMoZGZbY29sc10pLCBkZWNyZWFzaW5nPVRSVUUpCiAgcmV0dXJuKGxpc3QoJ3Bsb3RfcHJvcG9ydGlvbnMnPXBsb3RfcHJvcG9ydGlvbnMsCiAgICAgICAgICAgICAgJ3RvcF9jb2xzJz10b3BfY29scykpICAKfQpgYGAKCmBgYHtyIGhlbHBlci10cmFuc2Zvcm1zfQojIEZyb20gTGVzc29uIDg6IFByZXByb2Nlc3NpbmcgRGF0YSAoMjIuIFNrZXduZXNzIGFuZCBQb3dlciBUcmFuc2Zvcm1hdGlvbnMpCiMgQWxzbyBmcm9tIExlc3NvbmcgMTA6IExvZ2lzdGljIFJlZ3Jlc3Npb24gKDE5LiBJbmNyZWFzaW5nIERhdGEgRGltZW5zaW9uYWxpdHkpCiMgVGhlIFRyYW5zZm9ybXMgd2UgYXJlIGdvaW5nIHRvIHVzZSBhcmUKIyAxLiB4XjIKIyAyLiBsb2coeCkgKGluIHRoZW9yeSB3ZSBjb3VsZCBjaG9vc2UgZGlmZmVyZW5jZSB2YWx1ZXMgb2YgbGFtYmRhIHRvIGNob29zZSBkaWZmZXJlbnQgdHlwZXMgb2YgcG93ZXIgdHJhbnNmb3JtYXRpb25zLiBJbiB0aGlzIGNhc2UgZm9yIHNpbXBsaWNpdHksIHdlIHdpbGwgc3RpY2sgd2l0aCBsb2cgdHJhbnNmb3JtYXRpb25zIChlLmcuIGxhbWJkYSA9IDApKQoKIyBIZWxwZXIgZnVuY3Rpb24gdG8gY3JlYXRlIHBvd2VyIHRyYW5zZm9ybXMKY3JlYXRlX3Bvd2VyX3RyYW5zZm9ybXMgPSBmdW5jdGlvbihkZiwgY29sdW1uKXsKICAjIEtleXdvcmQgQXJnczoKICAjICBkZjogRGF0YUZyYW1lIHdpdGggZGF0YSB0aGF0IHlvdSB3YW50IHRvIGFkZCBwb3dlciB0cmFuc2Zvcm1hdGlvbnMgdG8KICAjICBjb2x1bW46IENvbHVtbiB3aXRoaW4gZGYgdGhhdCB5b3Ugd2FudCB0byBkbyBwb3dlciB0cmFuc2Zvcm1hdGlvbnMgdG8KICAjIFJldHVybnM6CiAgIyAgZGY6IERhdGFGcmFtZSB3aXRoIG5ldyBjb2x1bW5zIGFzIHRyYW5zZm9ybWF0aW9ucwogIAogICMgUG93ZXIgVHJhbnNmb3JtcwogICMgRm9yIG5vdywgbGV0J3Mgb25seSBjb25zaWRlciBwb3dlciB0cmFuc2Zvcm1hdGlvbnMgd2hlcmUgbGFtYmRhID0gMiBvciAtMS4gSW4gdGhlb3J5CiAgIyB3ZSBjb3VsZCB0cnkgaW5maW5pdGUgdmFsdWVzIG9mIGxhbWJkYSB3aXRoIHNvbWUga2luZCBvZiBjcm9zcyB2YWxpZGF0aW9uIGFwcHJvYWNoIHRvIHR1bmUgdGhpcwogICMgYnV0IGZvciBzaW1wbGljaXR5IGxldCdzIGp1c3QgdXNlIGludmVyc2UgYW5kIHF1YWRyYXRpYy4KICBkZltbcGFzdGUwKGNvbHVtbiwgJzInKV1dID0gKGRmW1tjb2x1bW5dXSBeIDIgLSAxKSAvIDIgICMgbGFtYmRhID0gMgogIGRmW1twYXN0ZTAoY29sdW1uLCAnbmVnMScpXV0gPSAtMSAqIChkZltbY29sdW1uXV0gXiAoLTEpIC0gMSkgLyAoLTEpICAjIGxhbWJkYSA9IC0xCiAgZGZbW3Bhc3RlMCgnbG9nJywgY29sdW1uKV1dID0gbG9nKGRmW1tjb2x1bW5dXSkKICByZXR1cm4oZGYpCn0KCmNyZWF0ZV9iaW5uaW5nX3RyYW5zZm9ybXMgPSBmdW5jdGlvbih4LCB0aHJlc2hvbGQpewogICMgS2V5d29yZCBBcmdzOgogICMgIHg6IFZlY3RvciBvZiBkYXRhIHlvdSB3YW50IHRvIHRyYW5zZm9yCiAgIyAgdGhyZXNob2xkOiBUaHJlc2hvbGQgb3ZlciB3aGljaCB0cmFuc2Zvcm1lZCB2ZWN0b3IgZXF1YWxzIDEsIGVsc2UgMAogICMgUmV0dXJuczoKICAjICBiaW5uZWQ6IE5ldyBiaW5uZWQgdHJhbnNmb3JtZWQgdmVjdG9yCiAgCiAgIyBQb3dlciBUcmFuc2Zvcm1zCiAgYmlubmVkID0gYXMubnVtZXJpYyh4ID49IHRocmVzaG9sZCkKICByZXR1cm4oYmlubmVkKQp9CmBgYAoKCmBgYHtyIHdhcm5pbmc9RkFMU0V9CiMgVE9ETzogQnVpbGQgJiBldmFsdWF0ZSBtb2RlbCAyICh0cmFuc2Zvcm1lZCBudW1lcmljIHZhcmlhYmxlcyBvbmx5KQoKIyBDcmVhdGluZyBuZXcgdmFyaWFibGVzCmRmJGlzX2J1ZGdldF9ncmVhdGVyX3RoYW5fNjBtID0gYXMubnVtZXJpYyhkZiRCdWRnZXQgPj0gNjAwMDAwMDApCmRmJGltZGJSYXRpbmdfZ3JlYXRlcl90aGFuXzc1ID0gYXMubnVtZXJpYyhkZiRpbWRiUmF0aW5nID49IDcuNSkKZGYgPSBjcmVhdGVfcG93ZXJfdHJhbnNmb3JtcyhkZiwgJ0J1ZGdldCcpCmRmID0gY3JlYXRlX3Bvd2VyX3RyYW5zZm9ybXMoZGYsICdSdW50aW1lJykKZGYgPSBjcmVhdGVfcG93ZXJfdHJhbnNmb3JtcyhkZiwgJ2ltZGJSYXRpbmcnKQpkZiA9IGNyZWF0ZV9wb3dlcl90cmFuc2Zvcm1zKGRmLCAnaW1kYlZvdGVzJykKZGYgPSBjcmVhdGVfcG93ZXJfdHJhbnNmb3JtcyhkZiwgJ3JlbGVhc2VkX3llYXInKQoKIyBBZGRpbmcgYmlucyBieSB5ZWFycwpkZiRlYXJseV8yMDAwcyA9IGFzLm51bWVyaWMoZGYkcmVsZWFzZWRfeWVhciA8PSAyMDA0KQpkZiRtaWRfMjAwMHMgPSBhcy5udW1lcmljKGRmJHJlbGVhc2VkX3llYXIgPj0gMjAwNSAmIGRmJHJlbGVhc2VkX3llYXIgPD0gMjAwOSkKZGYkcG9zdF8yMDEwID0gYXMubnVtZXJpYyhkZiRyZWxlYXNlZF95ZWFyID49IDIwMTApCgojIEFkZGluZyBiaW5zIGJ5IFJ1bnRpbWUKZGYkUnVudGltZVVuZGVyNzUgPSBhcy5udW1lcmljKGRmJFJ1bnRpbWUgPD0gNzUpCmRmJFJ1bnRpbWVCZXR3ZWVuNzVfMTI1ID0gYXMubnVtZXJpYyhkZiRSdW50aW1lID4gNzUgJiBkZiRSdW50aW1lIDw9IDEyNSkKZGYkUnVudGltZUdyZWF0ZXIxMjUgPSBhcy5udW1lcmljKGRmJFJ1bnRpbWUgPiAxMjUpCgojICJSZWNyZWF0ZSIgdGhlIHRyYWluaW5nIGFuZCB0ZXN0IGRmIHdpdGggdGhlIHNhbWUgaW5kaWNlcyB0byBpbmNsdWRlIHRoZSBuZXcgY29sdW1ucwp0cmFpbl9kZiA8LSBkZlt0cmFpbl9pZHgsIF0KdGVzdF9kZiAgPC0gZGZbLXRyYWluX2lkeCwgXQoKbW9kZWwyX2NvbHMgPSBjKG1vZGVsMV9jb2xzLCBjKCdpbWRiUmF0aW5nX2dyZWF0ZXJfdGhhbl83NScsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAnaW1kYlJhdGluZzInLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgJ2lzX2J1ZGdldF9ncmVhdGVyX3RoYW5fNjBtJywKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICdSdW50aW1lVW5kZXI3NScsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAnUnVudGltZUJldHdlZW43NV8xMjUnLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgJ1J1bnRpbWVHcmVhdGVyMTI1JywKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICdSdW50aW1lMicsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAnZWFybHlfMjAwMHMnLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgJ21pZF8yMDAwcycsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAncG9zdF8yMDEwJwogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgKSkKbW9kZWwyX2V4cCA9IHBhc3RlKCdHcm9zc34nLCBwYXN0ZShtb2RlbDJfY29scywgY29sbGFwc2U9JysnKSkKCmV2YWxfbW9kZWwyID0gZXZhbF9tb2RlbCh0cmFpbl9kZiwgdGVzdF9kZiwgbW9kZWwyX2V4cCwgdHJhaW5fc2l6ZXMsIG51bV9yZXBlYXQ9MTApCmRmX2V2YWxfbW9kMiA9IGV2YWxfbW9kZWwyJGRmX2V2YWwKYGBgCgoqKlEqKjogRXhwbGFpbiB3aGljaCB0cmFuc2Zvcm1hdGlvbnMgeW91IHVzZWQgYW5kIHdoeSB5b3UgY2hvc2UgdGhlbS4KCioqQSoqOgpJbiBhZGRpdGlvbiB0byB0aGUgZmVhdHVyZXMgZnJvbSBQYXJ0IDEsIEkgY3JlYXRlZCB0aGUgZm9sbG93aW5nIG5ldyBmZWF0dXJlcwoKMS4gQmlubmVkIGltZGJSYXRpbmc6IENyZWF0ZWQgYW4gaW5kaWNhdG9yIHZhcmlhYmxlIGZvciBpZiB0aGUgaW1kYlJhdGluZyB3YXMgZ3JlYXRlciB0aGFuIDcuNQoyLiBQb3dlciBUcmFuc2Zvcm1hdGlvbiBmb3IgaW1kYlJhdGluZzogbGFtYmRhPTIKMy4gQmlubmVkIEJ1ZGdldDogQ3JlYXRlZCBhbiBpbmRpY2F0b3IgdmFyaWFibGUgZm9yIGlmIGJ1ZGdldCBncmVhdGVyIHRoYW4gNjAgbWlsbGlvbgo0LiBCaW5uZWQgUnVudGltZSAodW5kZXIgNzUsIGJldHdlZW4gNzUgYW5kIDEyNSwgYW5kIG92ZXIgMTI1KQo1LiBQb3dlciBUcmFuc2Zvcm1hdGlvbiBmb3IgUnVudGltZTogbGFtYmRhPTIKNi4gQmlubmVkIFJlbGVhc2VkIFllYXIgKGJldHdlZW4gMjAwMCBhbmQgMjAwNDsgMjAwNSBhbmQgMjAwOTsgMjAxMCBhbmQgYWZ0ZXIpCgpGaXJzdCwgSSBjcmVhdGVkIGEgYmlubmVkIHZhcmlhYmxlIGZvciBpbWRiUmF0aW5nIGF0IDcuNS4gKE5vdGUsIGxhdGVyIG9uIGluIHBhcnQgNSBhcyB3ZSBleHBsb3JlIG1vcmUgaW50ZXJlc3RpbmcgcmVsYXRpb25zaGlwcywgSSBpbnRlcmFjdCB0aGlzIGJpbm5lZCB2YXJpYWJsZSB3aXRoIGltZGJSYXRpbmcgdG8gY2FwdHVyZSBhIGRpZmZlcmVudCByZWxhdGlvbnNoaXAgYWZ0ZXIgNy41IC0tIG1vcmUgb24gdGhpcyBiZWxvdykuCgpJIGNob3NlIHRvIGxvb2sgYXQgaW1kYlJhdGluZyBiZWNhdXNlIEkgbm90aWNlZCBhbiBpbnRlcmVzdGluZyByZWxhdGlvbnNoaXAgd2l0aCBpbWRiUmF0aW5nIGFuZCBHcm9zcy4gSWYgeW91IGxvb2sgYXQgdGhlIHBsb3QgYmVsb3csIHdlIHBsb3QgdGhlIGF2ZXJhZ2UgR3Jvc3MgZ2l2ZW4gaW1kYlJhdGluZywgYW5kIG5vdGljZSB0aGF0IHRoZSByZWxhdGlvbnNoaXAgc2VlbXMgdG8gYmUgbGluZWFyIHVudGlsIGFib3V0IDcuNS4gVGhlbiB0aGUgYXZlcmFnZSBncm9zcyBpbmNyZWFzZXMgYSBsb3QuIFRoaXMgaXMgYW4gaW50ZXJlc3RpbmcgcmVsYXRpb25zaGlwIGJlY2F1c2UgbWF5YmUgbW92aWVzIGJlbG93IDcuNSByYXRpbmcgYXJlIG1vc3RseSBub3QgdGhhdCBncmVhdCBhbmQgc28gdGhlIHJlbGF0aW9uc2hpcCBpcyBsaW5lYXIuIEJ1dCBpZiBhIG1vdmllIGlzIHJlYWxseSByZWFsbHkgZ29vZCwgd2hlbiB0aGUgcmVsYXRpb25zaGlwIGJldHdlZW4gR3Jvc3MgYW5kIHJhdGluZyBjaGFuZ2VzLCB0aGF0IGlzIGEgcmVhbGx5IHJlYWxseSBnb29kIG1vdmllIGdyb3NzZXMgbXVjaCBtdWNoIG1vcmUuIFRodXMsIEkgY2hvc2UgdG8gY3JlYXRlIGEgYmlubmVkIHZhcmlhYmxlIGF0IGBpbWRiUmF0aW5nID0gNy41YC4gVGhpcyB3aWxsIGFsbG93IGZvciB0aGUgbW9kZWwgdG8gcG90ZW50aWFsbHkgY2FwdHVyZSBhIGRpZmZlcmVuY2UgaW4gdGhlIGVmZmVjdCBvZiByYXRpbmcgb24gYEdyb3NzYCBmb3IgbW92aWVzIHdpdGggcmF0aW5ncyBncmVhdGVyIHRoYW4gNy41LiBIb3dldmVyLCBmcm9tIHRoZSBncmFwaCwgbm90aWNlIHRoYXQgdGhlIHNsb3BlIG9mIHRoZSByZWxhdGlvbnNoaXAgaXMgbm90IGNvbnN0YW50IGJlZm9yZSBhbmQgYWZ0ZXIgNy41LiBUaHVzLCBpbiBwYXJ0IDUsIHdlIHdpbGwgYWxzbyBjcmVhdGUgYW4gaW50ZXJhY3Rpb24gdmFyaWFibGUgdG8gdHJ5IHRvIGNhcHR1cmUgYSBkaWZmZXJlbnQgcmVsYXRpb25zaGlwIGFmdGVyIGBpbWRiUmF0aW5nID0gNy41YCB3aXRoIGBpbWRiUmF0aW5nMmAgd2hlcmUgYGltZGJSYXRpbmcyYCBpcyB0aGUgc3F1YXJlZCB0ZXJtIG9mIGltZGJSYXRpbmcgKHNpbmNlIHRoZSByZWxhdGlvbnNoaXAgYWZ0ZXIgaW1kYlJhdGluZz03LjUgbG9va3MgZXhwb25lbnRpYWwuIEZ1cnRoZXJtb3JlLCBsb29raW5nIGF0IHRoZSBgR0dQYWlyc2AgZ3JhcGgsIGl0IGxvb2tzIGxpa2UgYGltZGJSYXRpbmcyYCBoYXMgYSBoaWdoZXIgbGluZWFyIGNvcnJlbGF0aW9uIHdpdGggYEdyb3NzYCB0aGFuIGBpbWRiUmF0aW5nYC4gVGhlIG90aGVyIHBvd2VyIHRyYW5zZm9ybWF0aW9ucyBkaWQgbm90IHNlZW0gdG8gaW5jcmVhc2UgbGluZWFyIGNvcnJlbGF0aW9uLikKCmBgYHtyIHdhcm5pbmc9RkFMU0V9CmdncGxvdChkZiwgYWVzKGltZGJSYXRpbmcsIEdyb3NzKSkgKwogIHN0YXRfc3VtbWFyeShmdW4ueSA9ICJtZWFuIiwgY29sb3VyID0gInJlZCIsIHNpemUgPSAyLCBnZW9tID0gInBvaW50IikgKwogIGdndGl0bGUoJ1JlbGF0aW9uc2hpcCBiZXR3ZWVuIEdyb3NzIGFuZCBpbWRiUmF0aW5nJykKCmdncGFpcnMoZGZbYygnaW1kYlJhdGluZycsICdpbWRiUmF0aW5nMicsICdpbWRiUmF0aW5nbmVnMScsICdsb2dpbWRiUmF0aW5nJywgJ0dyb3NzJyldKQpgYGAKCk5leHQsIEkgbG9va2VkIGF0IHRoZSByZWxhdGlvbnNoaXAgYmV0d2VlbiBgR3Jvc3NgIGFuZCBgaW1kYlZvdGVzYC4gRnJvbSB0aGUgZ3JhcGhzIGJlbG93LCBpdCBkb2Vzbid0IGxvb2sgbGlrZSB0aGVyZSBhcmUgbWVhbmluZ2Z1bCBiaW5zIHRoYXQgd291bGQgYWRkIHZhbHVlLiBGdXJ0aGVybW9yZSwgbG9va2luZyBhdCB0aGUgYEdHUGFpcnNgIHBsb3QsIG5vbmUgb2YgdGhlIHBvd2VyIHRyYW5zZm9ybWF0aW9ucyBhZGQgaGlnaGVyIGxpbmVhciBjb3JyZWxhdGlvbiB3aXRoIEdyb3NzLCBzbyBpdCBkb2VzIG5vdCBtYWtlIHNlbnNlIHRvIGFkZCBhZGRpdGlvbmFsIHRyYW5zZm9ybWF0aW9ucyB3aXRoIGBpbWRiVm90ZXNgIGludG8gb3VyIG1vZGVsLgoKYGBge3Igd2FybmluZz1GQUxTRX0KZ2dwbG90KGRmLCBhZXMoaW1kYlZvdGVzLCBHcm9zcykpICsKICBzdGF0X3N1bW1hcnkoZnVuLnkgPSAibWVhbiIsIGNvbG91ciA9ICJyZWQiLCBzaXplID0gMiwgZ2VvbSA9ICJwb2ludCIpICsKICBnZ3RpdGxlKCdSZWxhdGlvbnNoaXAgYmV0d2VlbiBHcm9zcyBhbmQgaW1kYlZvdGVzJykKCmdncGFpcnMoZGZbYygnaW1kYlZvdGVzJywgJ2ltZGJWb3RlczInLCAnaW1kYlZvdGVzbmVnMScsICdsb2dpbWRiVm90ZXMnLCAnR3Jvc3MnKV0pCmBgYAoKTmV4dCwgSSBsb29rIGF0IHRoZSByZWxhdGlvbnNoaXAgYmV0d2VlbiBCdWRnZXQgYW5kIEdyb3NzLiBUaGlzIGlzIGJlY2F1c2UgaWYgd2UgcmVtZW1iZXIgYmFjayB0byBQcm9qZWN0IDEsIGhpZ2hlciBidWRnZXQgZmlsbXMgdGVuZGVkIHRvIGdyb3NzIG11Y2ggaGlnaGVyLiBMb29raW5nIGF0IHRoZSBncmFwaHMgYmVsb3csIGl0IHNlZW1zIGxpa2UgJDYwTSB3b3VsZCBiZSBhbiBpbnRlcmVzdGluZyBiaW4uIEhvd2V2ZXIsIHdlIGFsc28gcGxvdCB0aGUgR0dQYWlycyBwbG90IG9mIGBCdWRnZXQyYCAocG93ZXIgdHJhbnNmb3JtYXRpb24gd2l0aCBsYW1iZGEgPSAyKSwgYEJ1ZGdldG5lZzFgIChwb3dlciB0cmFuc2Zvcm1hdGlvbiB3aXRoIGxhbWJkYSA9IC0xKSwgYGxvZ0J1ZGdldGAgKHBvd2VyIHRyYW5zZm9ybWF0aW9uIHdpdGggbGFtYmRhID0gMCkgYW5kIGBCdWRnZXRgIGFnYWluc3QgYEdyb3NzYCBhbmQgeW91IGNhbiBzZWUgYXQgbGVhc3Qgd2l0aCB0aGUgbGluZWFyIGNvcnJlbGF0aW9uLCBub25lIG9mIHRoZSBwb3dlciB0cmFuc2Zvcm1hdGlvbnMgb2ZmZXIgYmV0dGVyIGxpbmVhciBjb3JyZWxhdGlvbiB3aXRoIGBHcm9zc2AgdGhhbiBgQnVkZ2V0YC4gVGh1cywgd2UgY2hvb3NlIG5vdCB0byBhZGQgYW55IG9mIHRoZSBwb3dlciB0cmFuc2Zvcm1hdGlvbnMgb2YgYEJ1ZGdldGAgaW50byB0aGlzIG1vZGVsLgoKYGBge3J9CiMgRGlzdHJpYnV0aW9uIG9mIEdyb3NzIGJ5IEJ1ZGdldAojIE5vdyB3ZSBuZWVkIHRvIHNob3cgZGlzdHJpYnV0aW9uIG9mIHJ1bnRpbWUgYnkgQnVkZ2V0LiBEbyBhIGJveHBsb3QgZ3JvdXBlZCBieSBidWRnZXQKZGYkYnVkZ2V0X3JvdW5kZWQgPSByb3VuZF9hbnkoZGYkQnVkZ2V0LCAxMDAwMDAwMCwgZiA9IGZsb29yKQpkZiRidWRnZXRfcm91bmRlZFtkZiRCdWRnZXQgPj0gNjAwMDAwMDBdID0gNjAwMDAwMDAKZGYkYnVkZ2V0X3JvdW5kZWQgPSBhcy5jaGFyYWN0ZXIoZGYkYnVkZ2V0X3JvdW5kZWQpCmRmJGJ1ZGdldF9yb3VuZGVkW2RmJGJ1ZGdldF9yb3VuZGVkID09ICc2MDAwMDAwMDAnXSA9ICdPdmVyIDYwTScKCmdncGxvdChkZiwgYWVzKGFzLmZhY3RvcihidWRnZXRfcm91bmRlZCksIEdyb3NzKSkgKwogIGdlb21fYm94cGxvdCgpICsKICBjb29yZF9mbGlwKCkgKwogIHNjYWxlX3hfZGlzY3JldGUoIkJ1ZGdldCBSb3VuZGVkIikgKwogIGdndGl0bGUoJ0Rpc3RyaWJ1dGlvbiBvZiBHcm9zcyBieSBCdWRnZXQnKQoKZ2dwbG90KGRmLCBhZXMoQnVkZ2V0LCBHcm9zcykpICsKICBzdGF0X3N1bW1hcnkoZnVuLnkgPSAibWVhbiIsIGNvbG91ciA9ICJyZWQiLCBzaXplID0gMiwgZ2VvbSA9ICJwb2ludCIpCgpnZ3BhaXJzKGRmW2MoJ0J1ZGdldCcsICdCdWRnZXQyJywgJ0J1ZGdldG5lZzEnLCAnbG9nQnVkZ2V0JywgJ0dyb3NzJyldKQpgYGAKCk5leHQsIHdlIGxvb2sgYXQgdGhlIHJlbGF0aW9uc2hpcCBiZXR3ZWVuIFJ1bnRpbWUgYW5kIEdyb3NzLiBUaGVyZSBhcmUgYSBmZXcgdGhpbmdzIHRoYXQgYXJlIGludGVyZXN0aW5nIGhlcmUuIE5vdGljZSBmaXJzdCB0aGF0IGlmIHdlIGxvb2sgYXQgdGhlIHJlbGF0aW9uc2hpcCBiZXR3ZWVuIGBHcm9zc2AgYW5kIGBSdW50aW1lYCB0aGVyZSBzZWVtIHRvIGJlIGEgZmV3IGludGVyZXN0aW5nICJjdXQgcG9pbnRzIi4gRm9yIGBSdW50aW1lYCBiZXR3ZWVuIDAgYW5kIDc1ICh1cCB1bnRpbCB0aGUgb3JhbmdlIGRvdHRlZCBsaW5lKSwgdGhlIHJlbGF0aW9uc2hpcCBzZWVtcyB0byBiZSBmbGF0LiBGb3IgYFJ1bnRpbWVgIGJldHdlZW4gNzUgYW5kIDEyNSwgdGhlIHJlbGF0aW9uc2hpcCBpcyBsaW5lYXJseSBwb3NpdGl2ZWx5IHNsb3BlZC4gRmluYWxseSwgZm9yIGBSdW50aW1lYCBtb3JlIHRoYW4gMTI1IChvdmVyIDIgaG91cnMpLCB0aGUgcmVsYXRpb25zaGlwIHdpdGggR3Jvc3Mgc2VlbXMgdG8gYmUgdmVyeSBoaWdobHkgdmFyaWFibGUuIEZ1cnRoZXJtb3JlLCB0aGUgb3ZlcmFsbCByZWxhdGlvbnNoaXAgc2VlbXMgdG8gYmUgc2xpZ2h0bHkgcXVhZHJhdGljOyBub3RpY2UgdGhhdCBpbiB0aGUgYEdHUGFpcnNgIHBsb3QsIHRoZSBjb3JyZWxhdGlvbiBiZXR3ZWVuIGBSdW50aW1lMmAgKHBvd2VyIHRyYW5zZm9ybWF0aW9uIHdpdGggbGFtYmRhID0gMikgYW5kIGBHcm9zc2AgIGlzIGhpZ2hlciB0aGFuIHRoZSBjb3JyZWxhdGlvbiBiZXR3ZWVuIGBSdW50aW1lYCBhbmQgYEdyb3NzYC4gVGh1cywgd2UgY2hvb3NlIHRvIGFkZCBiaW5zIGZvciBgUnVudGltZWAgdXAgdG8gNzUsIGJldHdlZW4gNzUgYW5kIDEyNSwgYW5kIG92ZXIgMTI1LiBGdXJ0aGVybW9yZSwgd2UgY2hvb3NlIHRvIGFkZCBwb3dlciB0cmFuc2Zvcm1hdGlvbiBvZiBgUnVudGltZTJgLiBJbiBRdWVzdGlvbiA1LCB3ZSB3aWxsIGFsc28gZXhwbG9yZSBhZGRpbmcgaW50ZXJhY3Rpb25zIGJldHdlZW4gdGhlIGJpbm5lZCB2YXJpYWJsZSBhbmQgdGhlIHBvd2VyIHRyYW5zZm9ybWF0aW9uLCB3aGljaCBzZWVtcyBhcHByb3ByaWF0ZSBzaW5jZSB0aGUgcmVsYXRpb25zaGlwIGJldHdlZW4gYFJ1bnRpbWVgIGFuZCBgR3Jvc3NgIGRvZXMgbm90IHNlZW0gdG8gYmUgdGhlIHNhbWUgd2l0aGluIHRoZXNlIHBvaW50cy4KCmBgYHtyIHdhcm5pbmc9RkFMU0V9CmdncGxvdChkZiwgYWVzKFJ1bnRpbWUsIEdyb3NzKSkgKwogIHN0YXRfc3VtbWFyeShmdW4ueSA9ICJtZWFuIiwgY29sb3VyID0gInJlZCIsIHNpemUgPSAyLCBnZW9tID0gInBvaW50IikgKwogIGdlb21fdmxpbmUoeGludGVyY2VwdD03NSwgY29sb3I9J29yYW5nZScsIGxpbmV0eXBlPSdsb25nZGFzaCcpICsKICBnZW9tX3ZsaW5lKHhpbnRlcmNlcHQ9MTI1LCBjb2xvcj0nYmx1ZScsIGxpbmV0eXBlPSdsb25nZGFzaCcpICsKICBnZ3RpdGxlKCdBdmVyYWdlIEdyb3NzIHZzIFJ1bnRpbWUnKQoKZ2dwYWlycyhkZltjKCdSdW50aW1lJywgJ1J1bnRpbWUyJywgJ1J1bnRpbWVuZWcxJywgJ2xvZ1J1bnRpbWUnLCAnR3Jvc3MnKV0pCmBgYAoKRmluYWxseSwgdGhlIGxhc3QgbnVtZXJpY2FsIGNvbHVtbiBpcyBgcmVsZWFzZWRfeWVhcmAuIElmIHdlIHBsb3QgdGhlIGF2ZXJhZ2UgZ3Jvc3MgYW1vdW50IGJ5IGByZWxlYXNlZF95ZWFyYCB3ZSBzZWUgdGhlcmUgYXJlIGFjdHVhbGx5ICJjeWNsZXMiIHRoYXQgYXBwZWFyIHRvIGJlIGZvcm1pbmcuCgpgYGB7ciB3YXJuaW5nPUZBTFNFfQpnZ3Bsb3QoZGYsIGFlcyhyZWxlYXNlZF95ZWFyLCBHcm9zcykpICsKICBnZW9tX3BvaW50KHN0YXQ9J3N1bW1hcnknKSArCiAgZ2VvbV92bGluZSh4aW50ZXJjZXB0PTIwMDQuNSwgY29sb3I9J29yYW5nZScsIGxpbmV0eXBlPSdsb25nZGFzaCcpICsKICBnZW9tX3ZsaW5lKHhpbnRlcmNlcHQ9MjAwOS41LCBjb2xvcj0nYmx1ZScsIGxpbmV0eXBlPSdsb25nZGFzaCcpICsKICBnZ3RpdGxlKCdSZWxhdGlvbnNoaXAgYmV0d2VlbiBHcm9zcyBhbmQgUmVsZWFzZWQgWWVhcicpCgpnZ3BhaXJzKGRmW2MoJ3JlbGVhc2VkX3llYXInLCAncmVsZWFzZWRfeWVhcjInLCAncmVsZWFzZWRfeWVhcm5lZzEnLCAnbG9ncmVsZWFzZWRfeWVhcicsICdHcm9zcycpXSkKYGBgCgpGb3IgZXhhbXBsZSwgaW4gdGhlIGdyYXBoIGFib3ZlLCB3ZSBzZWUgdGhhdCBncm9zcyBhbW91bnRzIGFyZSBnZW5lcmFsbHkgaW5jcmVhc2luZyBiZXR3ZWVuIDIwMDAgdG8gMjAwNC4gVGhlbiBkcm9wcyBpbiAyMDA1IGFuZCBnZW5lcmFsbHkgcmlzZXMgYWdhaW4gYmV0d2VlbiAyMDA1IGFuZCAyMDA5LiBUaGVuLCBhdmVyYWdlIGdyb3NzIGFtb3VudHMgZHJvcHMgYWdhaW4gaW4gMjAxMC4gVGhpcyBpcyBsaWtlbHkgcmVmbGVjdGl2ZSBvZiB0aGUgZWNvbm9teS9idXNpbmVzcyBjeWNsZXMuIEVzcGVjaWFsbHkgdGhlIGRyb3AgYmV0d2VlbiAyMDA5IHRvIDIwMTAgY291bGQgYmUgYSByZWZsZWN0aW9uIG9mIHRoZSBHcmVhdCBSZWNlc3Npb24gd2hlcmUgcGVvcGxlIHNwZW50IGxlc3MgbW9uZXkgb24gZW50ZXJ0YWlubWVudC4gVGh1cywgSSB3YW50ZWQgdG8sIGluc3RlYWQgb2YganVzdCB1c2luZyBgcmVsZWFzZWRfeWVhcmAgYXMgYSBjb250aW51b3VzIHZhcmlhYmxlLCBjcmVhdGUgdGhyZWUgYmlucyBvZiB5ZWFyczogZWFybHkgMjAwMCdzLCBtaWQgMjAwMCdzLCBhbmQgcG9zdCAyMDEwLgoKSG93ZXZlciwgbm90aWNlIHRoYXQgaW4gdGhlIGBHR1BhaXJzYCBwbG90LCBub25lIG9mIHRoZSBwb3dlciB0cmFuc2Zvcm1hdGlvbnMgc2VlbWVkIHRvIG9mZmVyIGFueSBhZGRpdGlvbmFsIGxpbmVhciBjb3JyZWxhdGlvbjsgdGh1cyBJIGNob3NlIG5vdCB0byBhZGQgYW55IHBvd2VyIHRyYW5zZm9ybWF0aW9ucyBvZiBgcmVsZWFzZWRfeWVhcmAgaW50byB0aGUgbW9kZWwuCgoqKlEqKjogSG93IGRpZCB0aGUgUk1TRSBjaGFuZ2UgY29tcGFyZWQgdG8gVGFzayAxPwoKKipBKio6IAoKQmVsb3csIHdlIHBsb3QgdGhlIFRyYWluaW5nIGFuZCBUZXN0IFJNU0UgZm9yIE1vZGVsIDIuIEZ1cnRoZXJtb3JlLCB3ZSBjYW4gZGlyZWN0bHkgY29tcGFyZSB0aGUgYmVzdCB0ZXN0IFJNU0UgYmVsb3c6CgpgYGB7cn0KZ2dwbG90KGRhdGE9ZGZfZXZhbF9tb2QyLCBhZXMoeD10cmFpbl9zaXplLCB5PXJtc2VzKSkgKwogIGdlb21fbGluZShhZXMoY29sb3I9cm1zZV90eXBlLCBncm91cD1ybXNlX3R5cGUpKSArCiAgZ2d0aXRsZSgnTW9kZWwgMjogVHJhaW5pbmcgYW5kIFRlc3QgUk1TRScpCgpwcmludChldmFsX21vZGVsMSRiZXN0X3Rlc3Rfcm1zZSkKcHJpbnQoZXZhbF9tb2RlbDIkYmVzdF90ZXN0X3Jtc2UpCgppbXByb3ZlbWVudF9pbl9ybXNlX21vZGVsMiA9IGV2YWxfbW9kZWwyJGJlc3RfdGVzdF9ybXNlIC0gZXZhbF9tb2RlbDEkYmVzdF90ZXN0X3Jtc2UKcGVyY2VudGFnZV9pbXByb3ZlbWVudF9pbl9ybXNlX21vZGVsMiA9IDEuMCAqIGFicyhldmFsX21vZGVsMiRiZXN0X3Rlc3Rfcm1zZSAtIGV2YWxfbW9kZWwxJGJlc3RfdGVzdF9ybXNlKSAvIGV2YWxfbW9kZWwxJGJlc3RfdGVzdF9ybXNlCnByaW50KCdJbXByb3ZlbWVudCBpbiBSTVNFJykKcHJpbnQoaW1wcm92ZW1lbnRfaW5fcm1zZV9tb2RlbDIpCgojIEFzIGEgcGVyY2VudGFnZQpwcmludChwZXJjZW50YWdlX2ltcHJvdmVtZW50X2luX3Jtc2VfbW9kZWwyKQpgYGAKCk5vdGljZSB0aGF0IHRoZSBpbXByb3ZlbWVudCBpbiBtb2RlbDIgaXMgYHIge2ltcHJvdmVtZW50X2luX3Jtc2VfbW9kZWwyfWAgd2hpY2ggYWN0dWFsbHkgcmVwcmVzZW50cyBhIGByIHtwZXJjZW50YWdlX2ltcHJvdmVtZW50X2luX3Jtc2VfbW9kZWwyICogMTAwfWAgcGVyY2VudGFnZSBnYWluISBXaGlsZSB0aGlzIG1pZ2h0IHNlZW0gbW9kZXN0OyByZW1lbWJlciB0aGF0IHdlIGRpZCBub3RlIHNvbWUgbWVhbmluZ2Z1bCBpbnRlcmFjdGlvbnMgdGhhdCB3aWxsIGJlIGZ1cnRoZXIgZXhwbG9yZWQgaW4gUGFydCA1IQoKQWxzbywgd2UgY2FuIGxvb2sgYXQgd2hlcmUgdGhlIGJlc3QgdGVzdCBNU0Ugb2NjdXJlZCwgYXMgYSBmdW5jdGlvbiBvZiB0cmFpbmluZyBzaXplOgoKYGBge3J9CiMgQmVzdCB0ZXN0IFJNU0UKYmVzdF90ZXN0cm1zZV9tb2QyID0gZXZhbF9tb2RlbDIkYmVzdF90ZXN0X3Jtc2UKYmVzdF90cmFpbl9zaXplX21vZDIgPSBldmFsX21vZGVsMiRiZXN0X3RyYWluX3NpemUKYGBgCgpUaGUgYmVzdCBtZWFuIHRlc3QgUk1TRSB2YWx1ZSBJIG9ic2VydmVkIHdhcyBgciB7YmVzdF90ZXN0cm1zZV9tb2QyfWAgYW5kIEkgb2JzZXJ2ZWQgdGhhdCBhdCB0aGUgdHJhaW5pbmcgc2V0IHNpemUgYHIge2Jlc3RfdHJhaW5fc2l6ZV9tb2QyICogMTAwfWAgcGVyY2VudC4gTm90aWNlIGZyb20gdGhlIGdyYXBoIGFib3ZlIGFzIHdlbGwgdGhhdCB3ZSBzZWUgdGhhdCBUZXN0IFJNU0UgZ2VuZXJhbGx5IGRlY3JlYXNlcyB3aXRoIGhpZ2hlciB0cmFpbmluZyBzZXQuIFRoaXMgbWFrZXMgc2Vuc2UgYmVjYXVzZSB3aXRoIHZlcnkgbG93IHRyYWluaW5nIHNldCBzaXplLCB0aGUgbW9kZWwgd2lsbCBvdmVyZml0IHRoZSBtb3JlIGxpbWl0ZWQgZGF0YS4KCiMjIDMuIE5vbi1udW1lcmljIHZhcmlhYmxlcwoKV3JpdGUgY29kZSB0aGF0IGNvbnZlcnRzIGdlbnJlLCBhY3RvcnMsIGRpcmVjdG9ycywgYW5kIG90aGVyIGNhdGVnb3JpY2FsIHZhcmlhYmxlcyB0byBjb2x1bW5zIHRoYXQgY2FuIGJlIHVzZWQgZm9yIHJlZ3Jlc3Npb24gKGUuZy4gYmluYXJ5IGNvbHVtbnMgYXMgeW91IGRpZCBpbiBQcm9qZWN0IDEpLiBBbHNvIHByb2Nlc3MgdmFyaWFibGVzIHN1Y2ggYXMgYXdhcmRzIGludG8gbW9yZSB1c2VmdWwgY29sdW1ucyAoYWdhaW4sIGxpa2UgeW91IGRpZCBpbiBQcm9qZWN0IDEpLiBOb3cgdXNlIHRoZXNlIGNvbnZlcnRlZCBjb2x1bW5zIG9ubHkgdG8gYnVpbGQgeW91ciBuZXh0IG1vZGVsLgoKYGBge3IgaGVscGVyLW9uZS1ob3QtZW5jb2Rpbmd9CiMgQ3JlYXRlIGEgaGVscGVyIGZ1bmN0aW9uIHRvIGhlbHAgb25lLWhvdCBlbmNvZGUgY2VydGFpbiB0ZXh0IGNvbHVtbnMKIyBGb3IgZXhhbXBsZSwgR2VucmUsIEFjdG9ycywgRGlyZWN0b3JzCiMgQnkgZGVmYXVsdCB3ZSB3aWxsIGtlZXAgdGhlIHRvcCB0ZW4gb2YgZWFjaApjcmVhdGVfb25lX2hvdF9lbmNvZGVkID0gZnVuY3Rpb24oZGYsIGNvbHVtbiwgbnVtVG9wQ29scz0xMCl7CiAgIyBLZXl3b3JkIEFyZ3M6CiAgIyAgZGY6IERhdGFGcmFtZSB3aXRoIGRhdGEgYW5kIGNvbHVtbiB5b3Ugd2FudCB0byBvbmUtaG90LWVuY29kZQogICMgIGNvbHVtbjogQ29sdW1uIHdpdGhpbiB0aGUgZGYgdGhhdCB5b3Ugd2FudCB0byBvbmUtaG90IGVuY29kZQogICMgUmV0dXJuczoKICAjICBkZjogT3JpZ2luYWwgRGF0YUZyYW1lIHdpdGggbmV3bHkgYXBwZW5kZWQgY29sdW1ucyBvZiBudW1Ub3BDb2xzIGhpZ2hlc3Qgb2NjdXJpbmcgdmFsdWUgb2YgQ29sdW1uCiAgIyAgdG9wX2NvbHM6IFRoZSB0b3BfY29scyB0aGF0IHdlcmUgcmV0dXJuZWQKICAKICAjIEV4YW1wbGUgb2YgaG93IHRvIG9uZS1ob3QgZW5jb2RlOiBodHRwczovL3N0YWNrb3ZlcmZsb3cuY29tL3F1ZXN0aW9ucy8zOTc3ODM4Ny9yLWRhdGFmcmFtZS1vbmUtaG90LWVuY29kaW5nLW9mLWNvbHVtbi1jb250YWluaW5nLW11bHRpcGxlLXRlcm1zCiAgZGYgPSBjYmluZChkZiwgbXRhYnVsYXRlKHN0cnNwbGl0KGRmW1tjb2x1bW5dXSwgIiwiKSkpCiAgY29scyA9IHVuaXF1ZSh1bmxpc3Qoc3Ryc3BsaXQoZGZbW2NvbHVtbl1dLCAnLCcpKSkKICB0b3BfY29scyA9IHNvcnQoY29sU3VtcyhkZltjb2xzXSksIGRlY3JlYXNpbmc9VFJVRSkKICB0b3BfY29sc19uYW1lcyA9IG5hbWVzKHRvcF9jb2xzWzE6bnVtVG9wQ29sc10pCiAgbm9uX3RvcF9jb2xzX25hbWVzID0gc2V0ZGlmZihjb2xzLCB0b3BfY29sc19uYW1lcykKICAKICAjIFJlbW92ZSB0aGUgbm9uX3RvcF9jb2xzCiAgZGZbbm9uX3RvcF9jb2xzX25hbWVzXSA9IGxpc3QoTlVMTCkKICByZXR1cm4obGlzdCgnZGYnPWRmLAogICAgICAgICAgICAgICd0b3BfY29scyc9dG9wX2NvbHNfbmFtZXMpKSAKfQpgYGAKCmBgYHtyIHByb2Nlc3MtY2F0ZWdvcmljYWx9CiMgUHJvY2Vzc2luZyBHZW5yZSBDb2x1bW4gLS0gV29yayB0YWtlbiBmcm9tIFBSMQojIE5vdGU6IENoYW5nZSAnU2NpLUZpJyB0byAnU2NpRmknIGJlY2F1c2UgZGFzaGVzIGluIGNvbHVtbiBuYW1lcyBhcmUgcmVhbGx5IGFubm95aW5nIGluIFIKIyBBbHNvIGNoYW5naW5nIE4vQSBpbiBHZW5yZSB0byBuYSBzaW5jZSAvIGFyZSBhbHNvIHJlYWxseSBhbm5veWluZwpkZiRHZW5yZSA8LSBnc3ViKCdTY2ktRmknLCAnU2NpRmknLCBkZiRHZW5yZSkKCiMgUHJvY2Vzc2luZyBSYXRpbmcgQ29sdW1uIC0tIFdvcmsgdGFrZW4gZnJvbSBQUjEKZGYkcmF0ZWRfY2xlYW5lZCA9IGlmZWxzZShkZiRSYXRlZCAlaW4lIGMoJ0cnLCAnUEctMTMnLCAnUEcnLCAnUicsICdOQy0xNycpLCBkZiRSYXRlZCwgJ090aGVyJykKZGYkcmF0ZWRfY2xlYW5lZCA8LSBnc3ViKCdQRy0xMycsICdQRzEzJywgZGYkcmF0ZWRfY2xlYW5lZCkKZGYkcmF0ZWRfY2xlYW5lZCA8LSBnc3ViKCdOQy0xNycsICdOQzE3JywgZGYkcmF0ZWRfY2xlYW5lZCkKCiMgUHJlcHJvY2Vzc2luZyBDYXRlZ29yaWNhbCBWYXJpYWJsZXMgdG8gcmVtb3ZlIE4vQQpkZiRBY3RvcnMgPC0gZ3N1YignTi9BJywgJ25hJywgZGYkQWN0b3JzKQpkZiRDb3VudHJ5IDwtIGdzdWIoJ04vQScsICduYScsIGRmJENvdW50cnkpCmRmJERpcmVjdG9yIDwtIGdzdWIoJ04vQScsICduYScsIGRmJERpcmVjdG9yKQpkZiRHZW5yZSA8LSBnc3ViKCdOL0EnLCAnbmEnLCBkZiRHZW5yZSkKZGYkV3JpdGVyIDwtIGdzdWIoJ04vQScsICduYScsIGRmJFdyaXRlcikKZGYkcmF0ZWRfY2xlYW5lZCA8LSBnc3ViKCdOL0EnLCAnbmEnLCBkZiRyYXRlZF9jbGVhbmVkKQoKIyBQcmVwcm9jZXNzaW5nIENhdGVnb3JpY2FsIFZhcmlhYmxlcyB0byByZW1vdmUgc3BhY2VzCmRmJEFjdG9ycyA8LSBnc3ViKCcgJywgJycsIGRmJEFjdG9ycykKZGYkQ291bnRyeSA8LSBnc3ViKCcgJywgJycsIGRmJENvdW50cnkpCmRmJERpcmVjdG9yIDwtIGdzdWIoJyAnLCAnJywgZGYkRGlyZWN0b3IpCmRmJEdlbnJlIDwtIGdzdWIoJyAnLCAnJywgZGYkR2VucmUpCmRmJFdyaXRlciA8LSBnc3ViKCcgJywgJycsIGRmJFdyaXRlcikKZGYkcmF0ZWRfY2xlYW5lZCA8LSBnc3ViKCcgJywgJycsIGRmJHJhdGVkX2NsZWFuZWQpCgojIFByZXByb2Nlc3NpbmcgdG8gcmVtb3ZlIHBhcmFudGhlc2VzCmRmJFdyaXRlciA8LSBnc3ViKCdcXCgnLCAnJywgZGYkV3JpdGVyKQpkZiRXcml0ZXIgPC0gZ3N1YignXFwpJywgJycsIGRmJFdyaXRlcikKCiMgQ3JlYXRpbmcgb25lLWhvdCBlbmNvZGVkIENhdGVnb3JpY2FsIGNvbHVtbnMKZ2VucmVfb25lX2hvdF9lbmNvZGVkID0gY3JlYXRlX29uZV9ob3RfZW5jb2RlZChkZiwgJ0dlbnJlJywgMTApCmRmID0gZ2VucmVfb25lX2hvdF9lbmNvZGVkJGRmCnRvcF9nZW5yZXMgPSBnZW5yZV9vbmVfaG90X2VuY29kZWQkdG9wX2NvbHMKCmFjdG9yX29uZV9ob3RfZW5jb2RlZCA9IGNyZWF0ZV9vbmVfaG90X2VuY29kZWQoZGYsICdBY3RvcnMnLCAyMCkKZGYgPSBhY3Rvcl9vbmVfaG90X2VuY29kZWQkZGYKdG9wX2FjdG9ycyA9IGFjdG9yX29uZV9ob3RfZW5jb2RlZCR0b3BfY29scwoKZGlyZWN0b3Jfb25lX2hvdF9lbmNvZGVkID0gY3JlYXRlX29uZV9ob3RfZW5jb2RlZChkZiwgJ0RpcmVjdG9yJywgMjApCmRmID0gZGlyZWN0b3Jfb25lX2hvdF9lbmNvZGVkJGRmCnRvcF9kaXJlY3RvcnMgPSBkaXJlY3Rvcl9vbmVfaG90X2VuY29kZWQkdG9wX2NvbHMKCmNvdW50cnlfb25lX2hvdF9lbmNvZGVkID0gY3JlYXRlX29uZV9ob3RfZW5jb2RlZChkZiwgJ0NvdW50cnknLCAxMCkKZGYgPSBjb3VudHJ5X29uZV9ob3RfZW5jb2RlZCRkZgp0b3BfY291bnRyaWVzID0gY291bnRyeV9vbmVfaG90X2VuY29kZWQkdG9wX2NvbHMKCndyaXRlcl9vbmVfaG90X2VuY29kZWQgPSBjcmVhdGVfb25lX2hvdF9lbmNvZGVkKGRmLCAnV3JpdGVyJywgMjApCmRmID0gd3JpdGVyX29uZV9ob3RfZW5jb2RlZCRkZgp0b3Bfd3JpdGVycyA9IHdyaXRlcl9vbmVfaG90X2VuY29kZWQkdG9wX2NvbHMKCnJhdGluZ19vbmVfaG90X2VuY29kZWQgPSBjcmVhdGVfb25lX2hvdF9lbmNvZGVkKGRmLCAncmF0ZWRfY2xlYW5lZCcsIDUpCmRmID0gcmF0aW5nX29uZV9ob3RfZW5jb2RlZCRkZgp0b3BfcmF0aW5ncyA9IHJhdGluZ19vbmVfaG90X2VuY29kZWQkdG9wX2NvbHMKCiMgUmVtb3ZlIGNvbHVtbnMgZnJvbSBEYXRhRnJhbWUKZGYkR2VucmUgPSBOVUxMCmRmJERpcmVjdG9yID0gTlVMTApkZiRBY3RvcnMgPSBOVUxMCmRmJENvdW50cnkgPSBOVUxMCmRmJFJhdGluZyA9IE5VTEwKYGBgCgoKYGBge3IgcHJvY2Vzcy1hd2FyZHN9CiMgUHJvY2Vzc2luZyBBd2FyZHMgY29sdW1uCiMgV29yayB0YWtlbiBmcm9tIFBSMQpjb2xzID0gYygnVGl0bGUnLCAnQXdhcmRzJywgJ0dyb3NzJykKZGY2ID0gZGZbY29sc10KCmF3YXJkcyA9IHN0cmlfZXh0cmFjdF9hbGwoZGY2JEF3YXJkcywgcmVnZXg9IlxcZCsiKQphd2FyZF9sYWJlbHMgPSBzdHJpX2V4dHJhY3RfYWxsKHRvbG93ZXIoZGY2JEF3YXJkcyksIHJlZ2V4PSJ3aW58d29ufHdpbnN8bm9taW4iLCBpZ25vcmUuY2FzZT1UUlVFKQoKd2luc19saXN0ID0gYygpCm5vbWluYXRpb25zX2xpc3QgPSBjKCkKCmZvciAoaSBpbiAxOmxlbmd0aChhd2FyZHMpKXsKICAgIHdpbnMgPSAwCiAgICBub21pbmF0aW9ucyA9IDAKICAgIGZvcihqIGluIDE6bGVuZ3RoKGF3YXJkc1tbaV1dKSl7CiAgICAgICAgaWYoIWlzLm5hKGF3YXJkX2xhYmVsc1tbaV1dW2pdKSAmIChhd2FyZF9sYWJlbHNbW2ldXVtqXSA9PSAnd2lucycgfCBhd2FyZF9sYWJlbHNbW2ldXVtqXSA9PSAnd2luJyB8IGF3YXJkX2xhYmVsc1tbaV1dW2pdID09ICd3b24nKSl7CiAgICAgICAgICAgIHdpbnMgPSB3aW5zICsgYXMubnVtZXJpYyhhd2FyZHNbW2ldXVtqXSkKICAgICAgICB9IGVsc2UgaWYgKCFpcy5uYShhd2FyZF9sYWJlbHNbW2ldXVtqXSkgJiBhd2FyZF9sYWJlbHNbW2ldXVtqXSA9PSAnbm9taW4nKXsKICAgICAgICAgICAgbm9taW5hdGlvbnMgPSBub21pbmF0aW9ucyArIGFzLm51bWVyaWMoYXdhcmRzW1tpXV1bal0pCiAgICAgICAgfQogICAgfQogICAgd2luc19saXN0ID0gYyh3aW5zX2xpc3QsIHdpbnMpCiAgICBub21pbmF0aW9uc19saXN0ID0gYyhub21pbmF0aW9uc19saXN0LCBub21pbmF0aW9ucykKfQoKZGYkd2lucyA9IHdpbnNfbGlzdApkZiRub21pbmF0aW9ucyA9IG5vbWluYXRpb25zX2xpc3QKYGBgCgpgYGB7ciB3YXJuaW5nPUZBTFNFfQojIFRPRE86IEJ1aWxkICYgZXZhbHVhdGUgbW9kZWwgMyAoY29udmVydGVkIG5vbi1udW1lcmljIHZhcmlhYmxlcyBvbmx5KQojICJSZWNyZWF0ZSIgdGhlIHRyYWluaW5nIGFuZCB0ZXN0IGRmIHdpdGggdGhlIHNhbWUgaW5kaWNlcyB0byBpbmNsdWRlIHRoZSBuZXcgY29sdW1ucwp0cmFpbl9kZiA8LSBkZlt0cmFpbl9pZHgsIF0KdGVzdF9kZiAgPC0gZGZbLXRyYWluX2lkeCwgXQoKIyBUdXJuaW5nIGEgdmVjdG9yIG9mIGNvbHVtbnMgaW4gYSBtb2RlbCBleHByZXNzaW9uOiBodHRwczovL3N0YWNrb3ZlcmZsb3cuY29tL3F1ZXN0aW9ucy83OTg2ODA1L2hvdy1kby1pLXJ1bi1hLW11bHRpcGxlLWxpbmVhci1yZWdyZXNzaW9uLXVzaW5nLWEtdmVjdG9yLWFzLW15LXByZWRpY3RvcnMKbW9kZWwzX2NvbHMgPSBjKHRvcF9nZW5yZXMsIHRvcF9kaXJlY3RvcnMsIHRvcF9hY3RvcnMsIHRvcF9jb3VudHJpZXMsIHRvcF93cml0ZXJzLCB0b3BfcmF0aW5ncywgYygnd2lucycsICdub21pbmF0aW9ucycpKQptb2RlbDNfZXhwID0gcGFzdGUoJ0dyb3NzficsIHBhc3RlKG1vZGVsM19jb2xzLCBjb2xsYXBzZT0nKycpKQoKZXZhbF9tb2RlbDMgPSBldmFsX21vZGVsKHRyYWluX2RmLCB0ZXN0X2RmLCBtb2RlbDNfZXhwLCB0cmFpbl9zaXplcywgbnVtX3JlcGVhdD0xMCkKZGZfZXZhbF9tb2QzID0gZXZhbF9tb2RlbDMkZGZfZXZhbApgYGAKCioqUSoqOiBFeHBsYWluIHdoaWNoIGNhdGVnb3JpY2FsIHZhcmlhYmxlcyB5b3UgdXNlZCwgYW5kIGhvdyB5b3UgZW5jb2RlZCB0aGVtIGludG8gZmVhdHVyZXMuCgoqKkEqKjogCgpUaGUgY2F0ZWdvcmljYWwgdmFyaWFibGVzIHRoYXQgSSB1c2VkIHdlcmUKCjEuIEdlbnJlcyAodG9wIDEwKQoyLiBEaXJlY3RvcnMgKHRvcCAyMCkKMy4gQWN0b3JzICh0b3AgMjApCjQuIENvdW50cmllcyAodG9wIDEwKQo1LiBXcml0ZXJzICh0b3AgMjApCjYuIEF3YXJkcyAocHJvY2Vzc2VkIGludG8gd2lucyBhbmQgbm9taW5hdGlvbnMpCjcuIFJhdGluZ3MgKGUuZy4gUiwgUEcxMywgUEcsIEc7IHRvcCA1KQoKVGhlIHJlYXNvbiBmb3IgdGhlc2UgY2F0ZWdvcmljYWwgdmFyaWFibGVzIHdoeSBJIG9ubHkgdXNlIHRoZSB0b3AgMTAvMjAgaXMgYmVjYXVzZSBvZiBjdXJzZSBvZiBkaW1lbnNpb25hbGl0eS4gSWYgSSBpbmNsdWRlZCBhbGwgYXMgZmVhdHVyZXMsIEkgd291bGQgaGF2ZSB0b28gbXVjaCB2YXJpYXRpb24gKGFsYSBiaWFzLXZhcmlhbmNlIHRyYWRlb2ZmKS4gVG8gZW5jb2RlIHRoZW0gaW50byBmZWF0dXJlcywgZm9yIGVhY2ggZW50aXR5IChlLmcuIGdlbnJlLCBkaXJlY3RvciwgYWN0b3IpIEkgY3JlYXRlZCBhIG5ldyBvbmUtaG90IGVuY29kZWQgYmluYXJ5IHZhcmlhYmxlLiBFYWNoIHZhcmlhYmxlIHdvdWxkIGVxdWFsIDEgaWYgdGhlIG1vdmllIGNvbnRhaW5lZCB0aGF0IGVudGl0eSwgZWxzZSAwLiBQbGVhc2Ugc2VlIHRoZSBoZWxwZXIgZnVuY3Rpb24gSSBkZWZpbmVkIGBjcmVhdGVfb25lX2hvdF9lbmNvZGVkYCBmb3IgdGhlIGNvZGUgdGhhdCBJIHVzZWQuIAoKCioqUSoqOiBXaGF0IGlzIHRoZSBiZXN0IG1lYW4gdGVzdCBSTVNFIHZhbHVlIHlvdSBvYnNlcnZlZCwgYW5kIGF0IHdoYXQgdHJhaW5pbmcgc2V0IHNpemU/IEhvdyBkb2VzIHRoaXMgY29tcGFyZSB3aXRoIFRhc2sgMj8KCioqQSoqOiAKCmBgYHtyfQojIEJlc3QgdGVzdCBSTVNFCmJlc3RfdGVzdHJtc2VfbW9kMyA9IGV2YWxfbW9kZWwzJGJlc3RfdGVzdF9ybXNlCmJlc3RfdHJhaW5fc2l6ZV9tb2QzID0gZXZhbF9tb2RlbDMkYmVzdF90cmFpbl9zaXplCmBgYAoKVGhlIGJlc3QgbWVhbiB0ZXN0IFJNU0UgdmFsdWUgSSBvYnNlcnZlZCB3YXMgYHIge2Jlc3RfdGVzdHJtc2VfbW9kM31gIGFuZCBJIG9ic2VydmVkIHRoYXQgYXQgdGhlIHRyYWluaW5nIHNldCBzaXplIGByIHtiZXN0X3RyYWluX3NpemVfbW9kMyAqIDEwMH1gIHBlcmNlbnQuIE5vdGljZSBmcm9tIHRoZSBncmFwaCBhYm92ZSBhcyB3ZWxsIGZvciBtb2RlbCAzIHRoYXQgd2Ugc2VlIHRoYXQgVGVzdCBSTVNFIGdlbmVyYWxseSBkZWNyZWFzZXMgd2l0aCBoaWdoZXIgdHJhaW5pbmcgc2V0LiBBZ2FpbiwgdGhpcyBtYWtlcyBzZW5zZSBiZWNhdXNlIHdpdGggdmVyeSBsb3cgdHJhaW5pbmcgc2V0IHNpemUsIHRoZSBtb2RlbCB3aWxsIG92ZXJmaXQgdGhlIG1vcmUgbGltaXRlZCBkYXRhLgoKV2UgY2FuIGFsc28gY29tcGFyZSB0aGUgYmVzdCB0ZXN0IFJNU0UgZGlyZWN0bHkgYmV0d2VlbiBNb2RlbHMgMSwgMiwgYW5kIDMKCmBgYHtyfQpnZ3Bsb3QoZGF0YT1kZl9ldmFsX21vZDMsIGFlcyh4PXRyYWluX3NpemUsIHk9cm1zZXMpKSArCiAgZ2VvbV9saW5lKGFlcyhjb2xvcj1ybXNlX3R5cGUsIGdyb3VwPXJtc2VfdHlwZSkpICsKICBnZ3RpdGxlKCdNb2RlbCAzOiBUcmFpbmluZyBhbmQgVGVzdCBSTVNFJykKCnByaW50KGV2YWxfbW9kZWwxJGJlc3RfdGVzdF9ybXNlKQpwcmludChldmFsX21vZGVsMiRiZXN0X3Rlc3Rfcm1zZSkKcHJpbnQoZXZhbF9tb2RlbDMkYmVzdF90ZXN0X3Jtc2UpCmBgYAoKTm90aWNlIHRoYXQgY29tcGFyZWQgdG8gTW9kZWwgMiwgTW9kZWwgMyBwZXJmb3JtZWQgbXVjaCB3b3JzZS4gVGhpcyBpcyBiZWNhdXNlIHdlIG9ubHkgdXNlZCBjYXRlZ29yaWNhbCB2YXJpYWJsZXMgZm9yIE1vZGVsIDMgYW5kIHdlIGhhdmUgYSBwcmV0dHkgc3BhcnNlIGRhdGEgc2V0IGp1c3QgdXNpbmcgY2F0ZWdvcmljYWwgdmFyaWFibGVzLiBUaGlzIGFsc28gcG90ZW50aWFsbHkgc3VnZ2VzdHMgdGhhdCBqdXN0IHVzaW5nIHRoZSBjYXRlZ29yaWNhbCB2YXJpYWJsZXMgaXMgbm90IHN1ZmZpY2llbnQsIHdlIG5lZWQgdG8gY29tYmluZSBudW1lcmljYWwgYW5kIGV2ZW4gaW50ZXJhY3QgbnVtZXJpY2FsIGFuZCBjYXRlZ29yaWNhbCB2YXJpYWJsZXMgdG8gY29tZSB1cCB3aXRoIGFuIGV2ZW4gYmV0dGVyIG1vZGVsLgoKCiMjIDQuIE51bWVyaWMgYW5kIGNhdGVnb3JpY2FsIHZhcmlhYmxlcwoKVHJ5IHRvIGltcHJvdmUgdGhlIHByZWRpY3Rpb24gcXVhbGl0eSBhcyBtdWNoIGFzIHBvc3NpYmxlIGJ5IHVzaW5nIGJvdGggbnVtZXJpYyBhbmQgbm9uLW51bWVyaWMgdmFyaWFibGVzIGZyb20gKipUYXNrcyAyICYgMyoqLgoKYGBge3Igd2FybmluZz1GQUxTRX0KIyBUT0RPOiBCdWlsZCAmIGV2YWx1YXRlIG1vZGVsIDQgKG51bWVyaWMgJiBjb252ZXJ0ZWQgbm9uLW51bWVyaWMgdmFyaWFibGVzKQptb2RlbDRfY29scyA9IHVuaXF1ZShjKG1vZGVsMl9jb2xzLCBtb2RlbDNfY29scykpCm1vZGVsNF9leHAgPSBwYXN0ZSgnR3Jvc3N+JywgcGFzdGUobW9kZWw0X2NvbHMsIGNvbGxhcHNlPScrJykpCgpldmFsX21vZGVsNCA9IGV2YWxfbW9kZWwodHJhaW5fZGYsIHRlc3RfZGYsIG1vZGVsNF9leHAsIHRyYWluX3NpemVzLCBudW1fcmVwZWF0PTEwKQpkZl9ldmFsX21vZDQgPSBldmFsX21vZGVsNCRkZl9ldmFsCmBgYAoKKipRKio6IENvbXBhcmUgdGhlIG9ic2VydmVkIFJNU0Ugd2l0aCBUYXNrcyAyICYgMy4KCioqQSoqOiAKRm9yIE1vZGVsIDQsIEkgc2ltcGx5IGNvbWJpbmVkIChidXQgbm8gYWRkaXRpb25hbCBpbnRlcmFjdGlvbnMpIHRoZSB0ZXJtcyBmcm9tIFRhc2sgMiBhbmQgMyB0b2dldGhlci4gVGhlIFRyYWluaW5nIHZzIFRlc3QgUk1TRSBmb3IgTW9kZWwgNCBpcyBzaG93biBhYm92ZS4gSW4gYWRkaXRpb24sIHdlIGNhbiBkaXJlY3RseSBjb21wYXJlIHRoZSBiZXN0IHRlc3QgUk1TRXM6CgpgYGB7cn0KZ2dwbG90KGRhdGE9ZGZfZXZhbF9tb2Q0LCBhZXMoeD10cmFpbl9zaXplLCB5PXJtc2VzKSkgKwogIGdlb21fbGluZShhZXMoY29sb3I9cm1zZV90eXBlLCBncm91cD1ybXNlX3R5cGUpKSArCiAgZ2d0aXRsZSgnTW9kZWwgNDogVHJhaW5pbmcgYW5kIFRlc3QgUk1TRScpCgpwcmludChldmFsX21vZGVsMSRiZXN0X3Rlc3Rfcm1zZSkKcHJpbnQoZXZhbF9tb2RlbDIkYmVzdF90ZXN0X3Jtc2UpCnByaW50KGV2YWxfbW9kZWwzJGJlc3RfdGVzdF9ybXNlKQpwcmludChldmFsX21vZGVsNCRiZXN0X3Rlc3Rfcm1zZSkKYGBgCgpUaGUgaW1wcm92ZW1lbnQgb2YgTW9kZWwgNCBjb21wYXJlZCB0byBNb2RlbCAyIHdhczoKCmBgYHtyfQppbXByb3ZlbWVudF9pbl9ybXNlX21vZGVsNCA9IGV2YWxfbW9kZWw0JGJlc3RfdGVzdF9ybXNlIC0gZXZhbF9tb2RlbDIkYmVzdF90ZXN0X3Jtc2UKcGVyY2VudGFnZV9pbXByb3ZlbWVudF9pbl9ybXNlX21vZGVsNCA9IDEuMCAqIGFicyhldmFsX21vZGVsNCRiZXN0X3Rlc3Rfcm1zZSAtIGV2YWxfbW9kZWwyJGJlc3RfdGVzdF9ybXNlKSAvIGV2YWxfbW9kZWwyJGJlc3RfdGVzdF9ybXNlCnByaW50KCdJbXByb3ZlbWVudCBpbiBSTVNFJykKcHJpbnQoaW1wcm92ZW1lbnRfaW5fcm1zZV9tb2RlbDQpCgojIEFzIGEgcGVyY2VudGFnZQpwcmludChwZXJjZW50YWdlX2ltcHJvdmVtZW50X2luX3Jtc2VfbW9kZWw0KQpgYGAKCk5vdGljZSB0aGF0IHRoZSBpbXByb3ZlbWVudCBpbiBNb2RlbCA0IHdhcyBgciB7aW1wcm92ZW1lbnRfaW5fcm1zZV9tb2RlbDR9YCB3aGljaCByZXByZXNlbnRzIGEgZnVydGhlciBgciB7cGVyY2VudGFnZV9pbXByb3ZlbWVudF9pbl9ybXNlX21vZGVsNCAqIDEwMH1gIHBlcmNlbnQgaW1wcm92ZW1lbnQgb3ZlciBNb2RlbCAyISBXaGlsZSB0aGlzIGltcHJvdmVtZW50IG1pZ2h0IHNlZW0gbW9kZXN0LCBhZ2FpbiwgaW4gUGFydCA1LCB3ZSBleHBsb3JlIGhvdyB0byBpbXByb3ZlIHRoaXMgZXZlbiBmdXJ0aGVyIHdpdGggbWVhbmluZ2Z1bCBpbnRlcmFjdGlvbnMuIEZ1cnRoZXJtb3JlLCBub3RlIHRoYXQgdGhlcmUgaXMgYSBtYXNzaXZlIGltcHJvdmVtZW50IG92ZXIgUGFydCAzIHdoZXJlIHdlIGp1c3QgaGFkIGNhdGVnb3JpY2FsIHZhcmlhYmxlcy4gCgpgYGB7cn0KIyBCZXN0IHRlc3QgUk1TRQpiZXN0X3Rlc3RybXNlX21vZDQgPSBldmFsX21vZGVsNCRiZXN0X3Rlc3Rfcm1zZQpiZXN0X3RyYWluX3NpemVfbW9kNCA9IGV2YWxfbW9kZWw0JGJlc3RfdHJhaW5fc2l6ZQpgYGAKClRoZSBiZXN0IG1lYW4gdGVzdCBSTVNFIHZhbHVlIEkgb2JzZXJ2ZWQgZm9yIE1vZGVsIDQgd2FzIGByIHtiZXN0X3Rlc3RybXNlX21vZDR9YCBhbmQgSSBvYnNlcnZlZCB0aGF0IGF0IHRoZSB0cmFpbmluZyBzZXQgc2l6ZSBgciB7YmVzdF90cmFpbl9zaXplX21vZDQgKiAxMDB9YCBwZXJjZW50LiBOb3RpY2UgYWdhaW4sIHdlIHNlZSBhIHNpbWlsYXIgcGF0dGVybiBhcyBvdGhlciBtb2RlbHMuIEZyb20gdGhlIGdyYXBoIGFib3ZlLCBub3RpY2UgdGhhdCBmb3IgTW9kZWwgNCB0aGF0IHdlIHNlZSB0aGF0IFRlc3QgUk1TRSBnZW5lcmFsbHkgZGVjcmVhc2VzIHdpdGggaGlnaGVyIHRyYWluaW5nIHNldC4gQWdhaW4sIHRoaXMgbWFrZXMgc2Vuc2UgYmVjYXVzZSB3aXRoIHZlcnkgbG93IHRyYWluaW5nIHNldCBzaXplLCB0aGUgbW9kZWwgd2lsbCBvdmVyZml0IHRoZSBtb3JlIGxpbWl0ZWQgZGF0YS4KCiMjIDUuIEFkZGl0aW9uYWwgZmVhdHVyZXMKCk5vdyB0cnkgY3JlYXRpbmcgYWRkaXRpb25hbCBmZWF0dXJlcyBzdWNoIGFzIGludGVyYWN0aW9ucyAoZS5nLiBgaXNfZ2VucmVfY29tZWR5YCB4IGBpc19idWRnZXRfZ3JlYXRlcl90aGFuXzNNYCkgb3IgZGVlcGVyIGFuYWx5c2lzIG9mIGNvbXBsZXggdmFyaWFibGVzIChlLmcuIHRleHQgYW5hbHlzaXMgb2YgZnVsbC10ZXh0IGNvbHVtbnMgbGlrZSBgUGxvdGApLgoKYGBge3Igd2FybmluZz1GQUxTRX0KIyBUT0RPOiBCdWlsZCAmIGV2YWx1YXRlIG1vZGVsIDUgKG51bWVyaWMsIG5vbi1udW1lcmljIGFuZCBhZGRpdGlvbmFsIGZlYXR1cmVzKQoKIyBDcmVhdGluZyBpbnRlcmFjdGlvbiB2YXJpYWJsZXMgYmV0d2VlbiB2YXJpYWJsZXMsIGFzIGV4cGxvcmVkIGFuZCBkaXNjdXNzZWQgaW4gUGFydCAyCmRmJGltZGJSYXRpbmdfZ3JlYXRlcl90aGFuXzc1X2ltZGJSYXRpbmcgPSBkZiRpbWRiUmF0aW5nX2dyZWF0ZXJfdGhhbl83NSAqIGRmJGltZGJSYXRpbmcKZGYkaW1kYlJhdGluZ19ncmVhdGVyX3RoYW5fNzVfaW1kYlJhdGluZzIgPSBkZiRpbWRiUmF0aW5nX2dyZWF0ZXJfdGhhbl83NSAqIGRmJGltZGJSYXRpbmcyCmRmJGlzX2J1ZGdldF9ncmVhdGVyX3RoYW5fNjBtX2J1ZGdldCA9IGRmJGlzX2J1ZGdldF9ncmVhdGVyX3RoYW5fNjBtICogZGYkQnVkZ2V0CmRmJFJ1bnRpbWVfUnVudGltZVVuZGVyNzUgPSBkZiRSdW50aW1lICogZGYkUnVudGltZVVuZGVyNzUKZGYkUnVudGltZV9SdW50aW1lQmV0d2Vlbjc1XzEyNSA9IGRmJFJ1bnRpbWUgKiBkZiRSdW50aW1lQmV0d2Vlbjc1XzEyNQpkZiRSdW50aW1lX1J1bnRpbWVHcmVhdGVyMTI1ID0gZGYkUnVudGltZSAqIGRmJFJ1bnRpbWVHcmVhdGVyMTI1CmRmJHJlbGVhc2VkX3llYXJfZWFybHlfMjAwMHMgPSBkZiRyZWxlYXNlZF95ZWFyICogZGYkZWFybHlfMjAwMHMKZGYkcmVsZWFzZWRfeWVhcl9taWRfMjAwMHMgPSBkZiRyZWxlYXNlZF95ZWFyICogZGYkbWlkXzIwMDBzCmRmJHJlbGVhc2VkX3llYXJfcG9zdF8yMDEwID0gZGYkcmVsZWFzZWRfeWVhciAqIGRmJHBvc3RfMjAxMAoKIyBDcmVhdGluZyBGYW1pbHkgRnJpZW5kbHkgdmFyaWFibGUgdGhhdCBpcyBHLCBQRywgb3IgUEcxMy4gQWxzbyBhZGRpbmcgaW50ZXJhY3Rpb25zLiAKZGYkZmFtaWx5X2ZyaWVuZGx5ID0gYXMubnVtZXJpYyhkZiRHICsgZGYkUEcgKyBkZiRQRzEzID49IDEpCmRmJGZhbWlseV9mcmllbmRseV93aW5zID0gZGYkZmFtaWx5X2ZyaWVuZGx5ICogZGYkd2lucwpkZiRmYW1pbHlfZnJpZW5kbHlfbm9taW5hdGlvbnMgPSBkZiRmYW1pbHlfZnJpZW5kbHkgKiBkZiRub21pbmF0aW9ucwoKIyBNYWRlIHRvIERWRApkZiRtYWRlX3RvX2R2ZCA9IGFzLm51bWVyaWMoIWlzLm5hKGRmJERWRCkpCgojIE51bWJlciBvZiBMYW5ndWFnZXMKZGYkbnVtYmVyX29mX2xhbmd1YWdlcyA9IHNhcHBseShzdHJzcGxpdChkZltbJ0xhbmd1YWdlJ11dLCAiLCIpLCBsZW5ndGgpCgojICJSZWNyZWF0ZSIgdGhlIHRyYWluaW5nIGFuZCB0ZXN0IGRmIHdpdGggdGhlIHNhbWUgaW5kaWNlcyB0byBpbmNsdWRlIHRoZSBuZXcgY29sdW1ucwp0cmFpbl9kZiA8LSBkZlt0cmFpbl9pZHgsIF0KdGVzdF9kZiAgPC0gZGZbLXRyYWluX2lkeCwgXQoKYWRkaXRpb25hbF9tb2RlbDVfY29scyA9IGMoJ2ltZGJSYXRpbmdfZ3JlYXRlcl90aGFuXzc1X2ltZGJSYXRpbmcnLAogICAgICAgICAgICAgICAgICAgICAgICAgICAnaW1kYlJhdGluZ19ncmVhdGVyX3RoYW5fNzVfaW1kYlJhdGluZzInLAogICAgICAgICAgICAgICAgICAgICAgICAgICAnaXNfYnVkZ2V0X2dyZWF0ZXJfdGhhbl82MG1fYnVkZ2V0JywKICAgICAgICAgICAgICAgICAgICAgICAgICAgJ1J1bnRpbWVfUnVudGltZVVuZGVyNzUnLAogICAgICAgICAgICAgICAgICAgICAgICAgICAnUnVudGltZV9SdW50aW1lQmV0d2Vlbjc1XzEyNScsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICdSdW50aW1lX1J1bnRpbWVHcmVhdGVyMTI1JywKICAgICAgICAgICAgICAgICAgICAgICAgICAgJ3JlbGVhc2VkX3llYXJfZWFybHlfMjAwMHMnLAogICAgICAgICAgICAgICAgICAgICAgICAgICAncmVsZWFzZWRfeWVhcl9taWRfMjAwMHMnLAogICAgICAgICAgICAgICAgICAgICAgICAgICAncmVsZWFzZWRfeWVhcl9wb3N0XzIwMTAnLAogICAgICAgICAgICAgICAgICAgICAgICAgICAnZmFtaWx5X2ZyaWVuZGx5JywKICAgICAgICAgICAgICAgICAgICAgICAgICAgJ2ZhbWlseV9mcmllbmRseV93aW5zJywKICAgICAgICAgICAgICAgICAgICAgICAgICAgJ2ZhbWlseV9mcmllbmRseV9ub21pbmF0aW9ucycsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICdtYWRlX3RvX2R2ZCcsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICdudW1iZXJfb2ZfbGFuZ3VhZ2VzJykKbW9kZWw1X2NvbHMgPSB1bmlxdWUoYyhtb2RlbDRfY29scywgYWRkaXRpb25hbF9tb2RlbDVfY29scykpCm1vZGVsNV9leHAgPSBwYXN0ZSgnR3Jvc3N+JywgcGFzdGUobW9kZWw1X2NvbHMsIGNvbGxhcHNlPScrJykpCgpldmFsX21vZGVsNSA9IGV2YWxfbW9kZWwodHJhaW5fZGYsIHRlc3RfZGYsIG1vZGVsNV9leHAsIHRyYWluX3NpemVzLCBudW1fcmVwZWF0PTEwKQpkZl9ldmFsX21vZDUgPSBldmFsX21vZGVsNSRkZl9ldmFsCmBgYAoKKipRKio6IEV4cGxhaW4gd2hhdCBuZXcgZmVhdHVyZXMgeW91IGRlc2lnbmVkIGFuZCB3aHkgeW91IGNob3NlIHRoZW0uCgoqKkEqKjogCkluIGFkZGl0aW9uIHRvIHRoZSBmZWF0dXJlcyBmcm9tIHRoZSBwcmV2aW91cyBwYXJ0cywgSSBjcmVhdGVkIHRoZSBmb2xsb3dpbmcgbmV3IGludGVyYWN0aW9uIGZlYXR1cmVzLiBOb3RlIHRoYXQgdGhlIHJlYXNvbmluZyBiZWhpbmQgdGhlc2UgaW50ZXJhY3Rpb25zIHdlcmUgYWxyZWFkeSBwYXJ0bHkgZXhwbG9yZWQgaW4gUGFydCAyLCBhbmQgSSB3aWxsIGVsYWJvcmF0ZSBmdXJ0aGVyIGhlcmUKCjEuIEludGVyYWN0aW9uIG9mIGBpbWRiUmF0aW5nYCBhbmQgYGltZGJSYXRpbmdfZ3JlYXRlcl90aGFuXzc1YAoyLiBJbnRlcmFjdGlvbiBvZiBgaW1kYlJhdGluZzJgIGFuZCBgaW1kYlJhdGluZ19ncmVhdGVyX3RoYW5fNzVgCjMuIEludGVyYWN0aW9uIG9mIGBCdWRnZXRgIGFuZCBgaXNfYnVkZ2V0X2dyZWF0ZXJfdGhhbl82MG1gCjQuIEludGVyYWN0aW9uIG9mIGBSdW50aW1lYCBhbmQgYFJ1bnRpbWVVbmRlcjc1YCwgYFJ1bnRpbWVCZXR3ZWVuNzVfMTI1YCwgYW5kIGBSdW50aW1lR3JlYXRlcjEyNWAKNS4gSW50ZXJhY3Rpb24gb2YgYFJ1bnRpbWUyYCBhbmQgYFJ1bnRpbWVVbmRlcjc1YCwgYFJ1bnRpbWVCZXR3ZWVuNzVfMTI1YCwgYW5kIGBSdW50aW1lR3JlYXRlcjEyNWAKNi4gSW50ZXJhY3Rpb24gb2YgYHJlbGVhc2VkX3llYXJgIGFuZCBgZWFybHlfMjAwMHNgLCBgbWlkXzIwMDBzYCwgYW5kIGBwb3N0XzIwMTBgCgpGdXJ0aGVybW9yZSwgSSBleHBsb3JlIG90aGVyIHJlbGF0aW9uc2hpcHMgdGhhdCB3ZXJlIGVudGlyZWx5IHByZXZpb3VzbHkgdW5kaXNjdXNzZWQgc3VjaCBhcwoKNy4gYGZhbWlseV9mcmllbmRseWA6IERlZmluZWQgYXMgTVBBQSByYXRpbmcgb2YgRywgUEcsIG9yIFBHLTEzCjguIGBmYW1pbHlfZnJpZW5kbHlfd2luc2A6IEludGVyYWN0aW9uIG9mIGBmYW1pbHlfZnJpZW5kbHlgIGFuZCBgd2luc2AKOS4gYGZhbWlseV9mcmllbmRseV9ub21pbmF0aW9uc2A6IEludGVyYWN0aW9uIG9mIGBmYW1pbHlfZnJpZW5kbHlgIGFuZCBgbm9taW5hdGlvbnNgLgoxMC4gYG1hZGVfdG9fZHZkYDogSW5kaWNhdG9yIHZhcmlhYmxlIGZvciBpZiBtb3ZpZSB3YXMgbWFkZSB0byBEVkQsIGludHVpdGlvbiBiZWluZyBpZiBtYWRlIHRvIERWRCBwZXJoYXBzIGl0IHdhcyBhIHN1Y2Nlc3NmdWwgbW92aWUgYW5kIEdyb3NzZWQgbW9yZS4KMTEuIGBudW1iZXJfb2ZfbGFuZ3VhZ2VzYDogUGFyc2VkIHRoZSBgTGFuZ3VhZ2VgIGNvbHVtbiBhbmQgY291bnQgbnVtYmVyIG9mIGxhbmd1YWdlcyBtb3ZpZSBpcyBtYWRlLiBJbnR1aXRpb24gYmVpbmcgaWYgbXVsdGlwbGUgbGFuZ3VhZ2VzLCBpdCBpcyBhbiBpbnRlcm5hdGlvbmFsIHJlbGVhc2Uvc3VjY2VzcyBhbmQgdGh1cyBHcm9zc2VkIG1vcmUuCgpGb3IgdGhlIGZpcnN0IDYgaW50ZXJhY3Rpb25zLCB3ZSBleHBsb3JlZCB0aGVzZSBhIGJpdCBpbiBQYXJ0IDIsIGJ1dCBJJ2xsIGVsYWJvcmF0ZSBmdXJodGVyIGhlcmUuIFJlY2FsbCB0aGF0IGluIFBhcnQgMiwgSSBjcmVhdGVkIGEgYmlubmVkIHZhcmlhYmxlIGZvciBpbWRiUmF0aW5nIGF0IDcuNS4gTm93LCBJIHdpbGwgaW50ZXJhY3QgdGhpcyBiaW5uZWQgdmFyaWFibGUgd2l0aCBgaW1kYlJhdGluZ2AgYW5kIGBpbWRiUmF0aW5nMmAgdG8gY2FwdHVyZSBhIGRpZmZlcmVudCByZWxhdGlvbnNoaXAgYWZ0ZXIgNy41IChtb3JlIG9uIHRoaXMgYmVsb3cpLgoKSWYgd2UgcmUtbG9vayBhdCB0aGUgcmVsYXRpb25zaGlwIGJldHdlZW4gYGltZGJSYXRpbmdgIGFuZCBgR3Jvc3NgLCB3ZSBwbG90IHRoZSBhdmVyYWdlIEdyb3NzIGdpdmVuIGltZGJSYXRpbmcsIGFuZCBub3RpY2UgdGhhdCB0aGUgcmVsYXRpb25zaGlwIHNlZW1zIHRvIGJlIGxpbmVhciB1bnRpbCBhYm91dCA3LjUuIFRoZW4gdGhlIGF2ZXJhZ2UgZ3Jvc3MgaW5jcmVhc2VzIGEgbG90LiBUaGlzIGlzIGFuIGludGVyZXN0aW5nIHJlbGF0aW9uc2hpcCBiZWNhdXNlIG1heWJlIG1vdmllcyBiZWxvdyA3LjUgcmF0aW5nIGFyZSBtb3N0bHkgbm90IHRoYXQgZ3JlYXQgYW5kIHNvIHRoZSByZWxhdGlvbnNoaXAgaXMgbGluZWFyLiBCdXQgaWYgYSBtb3ZpZSBpcyByZWFsbHkgcmVhbGx5IGdvb2QsIHdoZW4gdGhlIHJlbGF0aW9uc2hpcCBiZXR3ZWVuIEdyb3NzIGFuZCByYXRpbmcgY2hhbmdlcywgdGhhdCBpcyBhIHJlYWxseSByZWFsbHkgZ29vZCBtb3ZpZSBncm9zc2VzIG11Y2ggbXVjaCBtb3JlLiBUaHVzLCBJIGNob3NlIHRvIGNyZWF0ZSBhIGJpbm5lZCB2YXJpYWJsZSBhdCBgaW1kYlJhdGluZyA9IDcuNWAgYW5kIGludGVyYWN0ZWQgaXQgd2l0aCBgaW1kYlJhdGluZ2AgYW5kIGBpbWRiUmF0aW5nMmAgdG8gdHJ5IHRvIGNhcHR1cmUgYSBkaWZmZXJlbnQgcmVsYXRpb25zaGlwIGFmdGVyIGBpbWRiUmF0aW5nID0gNy41YCwgd2hlcmUgYGltZGJSYXRpbmcyYCBpcyB0aGUgc3F1YXJlZCB0ZXJtIG9mIGltZGJSYXRpbmcgKHNpbmNlIHRoZSByZWxhdGlvbnNoaXAgYWZ0ZXIgaW1kYlJhdGluZz03LjUgbG9va3MgZXhwb25lbnRpYWwpLgoKYGBge3J9CmdncGxvdChkZiwgYWVzKGltZGJSYXRpbmcsIEdyb3NzKSkgKwogIHN0YXRfc3VtbWFyeShmdW4ueSA9ICJtZWFuIiwgY29sb3VyID0gInJlZCIsIHNpemUgPSAyLCBnZW9tID0gInBvaW50IikKYGBgCgpTZWNvbmQsIEkgbG9vayBhdCB0aGUgcmVsYXRpb25zaGlwIGJldHdlZW4gQnVkZ2V0IGFuZCBHcm9zcy4gVGhpcyBpcyBiZWNhdXNlIGlmIHdlIHJlbWVtYmVyIGJhY2sgdG8gUHJvamVjdCAxLCBoaWdoZXIgYnVkZ2V0IGZpbG1zIHRlbmRlZCB0byBncm9zcyBtdWNoIGhpZ2hlci4gTG9va2luZyBhdCB0aGUgZ3JhcGhzIGJlbG93LCBpdCBzZWVtcyBsaWtlICQ2ME0gd291bGQgYmUgYW4gaW50ZXJlc3RpbmcgYmluLCBhbmQgSSB3YW50ZWQgdG8gY2FwdHVyZSB0aGUgaW50ZXJhY3Rpb24gb2YgdGhlIGJpbm5lZCB2YXJpYWJsZSB3aXRoIGBCdWRnZXRgIHRvIGFsbG93IGZvciBhIGRpZmZlcmVuY2UgaW4gc2xvcGUgYWZ0ZXIgJDYwTS4gCgpgYGB7cn0KIyBEaXN0cmlidXRpb24gb2YgR3Jvc3MgYnkgQnVkZ2V0CiMgTm93IHdlIG5lZWQgdG8gc2hvdyBkaXN0cmlidXRpb24gb2YgcnVudGltZSBieSBCdWRnZXQuIERvIGEgYm94cGxvdCBncm91cGVkIGJ5IGJ1ZGdldApkZiRidWRnZXRfcm91bmRlZCA9IHJvdW5kX2FueShkZiRCdWRnZXQsIDEwMDAwMDAwLCBmID0gZmxvb3IpCmRmJGJ1ZGdldF9yb3VuZGVkW2RmJEJ1ZGdldCA+PSA2MDAwMDAwMF0gPSA2MDAwMDAwMApkZiRidWRnZXRfcm91bmRlZCA9IGFzLmNoYXJhY3RlcihkZiRidWRnZXRfcm91bmRlZCkKZGYkYnVkZ2V0X3JvdW5kZWRbZGYkYnVkZ2V0X3JvdW5kZWQgPT0gJzYwMDAwMDAwMCddID0gJ092ZXIgNjBNJwoKZ2dwbG90KGRmLCBhZXMoYXMuZmFjdG9yKGJ1ZGdldF9yb3VuZGVkKSwgR3Jvc3MpKSArCiAgZ2VvbV9ib3hwbG90KCkgKwogIGNvb3JkX2ZsaXAoKSArCiAgc2NhbGVfeF9kaXNjcmV0ZSgiQnVkZ2V0IFJvdW5kZWQiKSArCiAgZ2d0aXRsZSgnRGlzdHJpYnV0aW9uIG9mIEdyb3NzIGJ5IEJ1ZGdldCcpCgpnZ3Bsb3QoZGYsIGFlcyhCdWRnZXQsIEdyb3NzKSkgKwogIHN0YXRfc3VtbWFyeShmdW4ueSA9ICJtZWFuIiwgY29sb3VyID0gInJlZCIsIHNpemUgPSAyLCBnZW9tID0gInBvaW50IikKYGBgCgpOZXh0LCB3aXRoIHJlZ2FyZHMgdG8gYFJ1bnRpbWVgIHJlY2FsbCBmcm9tIFBhcnQgMiB3ZSBzYXcgYW4gaW50ZXJlc3RpbmcgcmVsYXRpb25zaGlwIGJldHdlZW4gdGhlIGJpbnMgb2YgcHJlLTc1LCBiZXR3ZWVuIDc1IGFuZCAxMjUsIGFuZCBhZnRlciAxMjUgKHJlcHJvZHVjZWQgdmlzdWFsIGJlbG93KS4gVG8gY2FwdHVyZSB0aGlzIGludGVyYWN0aW9uLCB3ZSBpbnRlcmFjdGVkIGBSdW50aW1lYCBhbmQgYFJ1bnRpbWUyYCB3aXRoIHRoZSBiaW5uZWQgdmFyaWFibGVzLgoKYGBge3Igd2FybmluZz1GQUxTRX0KZ2dwbG90KGRmLCBhZXMoUnVudGltZSwgR3Jvc3MpKSArCiAgc3RhdF9zdW1tYXJ5KGZ1bi55ID0gIm1lYW4iLCBjb2xvdXIgPSAicmVkIiwgc2l6ZSA9IDIsIGdlb20gPSAicG9pbnQiKSArCiAgZ2VvbV92bGluZSh4aW50ZXJjZXB0PTc1LCBjb2xvcj0nb3JhbmdlJywgbGluZXR5cGU9J2xvbmdkYXNoJykgKwogIGdlb21fdmxpbmUoeGludGVyY2VwdD0xMjUsIGNvbG9yPSdibHVlJywgbGluZXR5cGU9J2xvbmdkYXNoJykgKwogIGdndGl0bGUoJ0F2ZXJhZ2UgR3Jvc3MgdnMgUnVudGltZScpCmBgYAoKTmV4dCwgd2l0aCByZWdhcmRzIHRvIGByZWxlYXNlZF95ZWFyYCByZWNhbGwgZnJvbSBQYXJ0IDIgd2Ugc2F3IGFuIGludGVyZXN0aW5nIHJlbGF0aW9uc2hpcCwgcG90ZW50aWFsbHkgZXhwbGFpbmVkIGJ5IEVjb25vbWljIGJ1c2luZXNzIGN5Y2xlcy4gQmVsb3csIHdlIHJlcHJvZHVjZSB0aGUgdmlzdWFsaXphdGlvbiwgYW5kIHRvIGFsbG93IGZvciB0aGUgbW9kZWwgdG8gY2FwdHVyZSB0aGlzIGludGVyZXN0aW5nIGVmZmVjdCwgd2UgaW50ZXJhY3QgYHJlbGVhc2VkX3llYXJgIHdpdGggdGhlIGJpbm5lZCB2YXJpYWJsZXMuCgpgYGB7ciB3YXJuaW5nPUZBTFNFfQpnZ3Bsb3QoZGYsIGFlcyhyZWxlYXNlZF95ZWFyLCBHcm9zcykpICsKICBnZW9tX3BvaW50KHN0YXQ9J3N1bW1hcnknKSArCiAgZ2VvbV92bGluZSh4aW50ZXJjZXB0PTIwMDQuNSwgY29sb3I9J29yYW5nZScsIGxpbmV0eXBlPSdsb25nZGFzaCcpICsKICBnZW9tX3ZsaW5lKHhpbnRlcmNlcHQ9MjAwOS41LCBjb2xvcj0nYmx1ZScsIGxpbmV0eXBlPSdsb25nZGFzaCcpICsKICBnZ3RpdGxlKCdSZWxhdGlvbnNoaXAgYmV0d2VlbiBHcm9zcyBhbmQgUmVsZWFzZWQgWWVhcicpCmBgYAoKTmV4dCwgSSB3YW50ZWQgdG8gZXhwbG9yZSBNUEFBIFJhdGluZ3MgbW9yZSBiZWNhdXNlIEkgbm90aWNlZCBhIHByZXR0eSBzaWduaWZpY2FudCBkaWZmZXJlbmNlIGluIEdyb3NzIGJ5IGRpZmZlcmVudCBNUEFBIFJhdGluZ3MuIEluIHBhcnRpY3VsYXIsIHdlIGNhbiBzZWUgZnJvbSB0aGUgZ3JhcGggYmVsb3cgdGhhdDoKCmBgYHtyIHdhcm5pbmc9RkFMU0V9CmdncGxvdChkZiwgYWVzKHJhdGVkX2NsZWFuZWQsIEdyb3NzKSkgKwogIGdlb21fYmFyKHN0YXQ9J3N1bW1hcnknKQpgYGAKCkl0IGxvb2tzIGxpa2UgRywgUEcsIGFuZCBQRzEzIHJhdGVkIG1vdmllcyBncm9zcyBtdWNoIGhpZ2hlciBvbiBhdmVyYWdlIHRoYW4gb3RoZXIgTVBBQSByYXRlZCBtb3ZpZXMuIFRoaXMgbWFrZXMgc2Vuc2UgYXMgdGhlc2UgdHlwZXMgb2YgbW92aWVzIGFyZSBtb3JlICJmYW1pbHkgZnJpZW5kbHkiIGFuZCBjYW4gcmVsYXRlIHRvIGEgbXVjaCBiaWdnZXIgYXVkaWVuY2UuIFRodXMsIEkgY3JlYXRlZCBhIGJpbmFyeSB2YXJpYWJsZSBgZmFtaWx5X2ZyaWVuZGx5YCBhbmQgaW50ZXJhY3RlZCBpdCB3aXRoIGB3aW5zYCBhbmQgYG5vbWluYXRpb25zYCB0byBzZWUgaWYgdGhlcmUgd2FzIGFueSBtZWFuaW5nZnVsIGludGVyYWN0aW9ucy4gSWYgd2UgbG9vayBhdCB0aGUgZ2dwYWlycywgaXQgbG9va3MgcHJvbWlzaW5nLiAKCmBgYHtyfQojIFBsb3R0aW5nIENvcnJlbGF0aW9uIFBsb3RzIG9mIHRoZSBkaWZmZXJlbnQgUmV2aWV3IG51bWJlciBDb2x1bW5zOgpnZ3BhaXJzKGRmW2MoJ2ZhbWlseV9mcmllbmRseScsICd3aW5zJywgJ2ZhbWlseV9mcmllbmRseV93aW5zJywgJ0dyb3NzJyldKQpnZ3BhaXJzKGRmW2MoJ2ZhbWlseV9mcmllbmRseScsICdub21pbmF0aW9ucycsICdmYW1pbHlfZnJpZW5kbHlfbm9taW5hdGlvbnMnLCAnR3Jvc3MnKV0pCmBgYAoKQW1vbmcgb3RoZXIgdGhpbmdzIGZyb20gdGhlIGBnZ3BhaXJzYCBncmFwaCBhYm92ZSwgeW91IGNhbiBzZWUgdGhhdCB0aGUgY29ycmVsYXRpb24gYmV0d2VlbiBgR3Jvc3NgIHZzLiBgd2luc2AgYW5kIGBub21pbmF0aW9uc2AgaXMgZmFpcmx5IGhpZ2ggKGFwcHJveGltYXRlbHkgMC4zIGZvciBlYWNoKSwgYnV0IGludGVyYWN0ZWQgd2l0aCBgZmFtaWx5X2ZyaWVuZGx5YCwgdGhlIGNvcnJlbGF0aW9uIGdvZXMgdXAgdG8gYWxtb3N0IDAuNSEgVGh1cywgaXQgc2VlbXMgbGlrZSB0aGVyZSBpcyBhIG1lYW5pbmdmdWwgaW50ZXJhY3Rpb24gYmV0d2VlbiBgd2luc2AsIGBub21pbmF0aW9uc2AgYW5kIGBmYW1pbHlfZnJpZW5kbHlgLiBUaGlzIGVzc2VudGlhbGx5IG1lYW5zIHRoYXQgZm9yIGZhbWlseSBmcmllbmRseSBtb3ZpZXMsIHdpbm5pbmcgYW5kL29yIGJlaW5nIG5vbWluYXRlZCBmb3IgYW4gYXdhcmQgaGFzIGZ1bmN0aW9uYWxseSBkaWZmZXJlbnQgcmVsYXRpb25zaGlwIHdpdGggR3Jvc3MgdGhhbiBub24tZmFtaWx5IGZyaWVuZGx5IG1vdmllcy4gCgoqKlEqKjogQ29tbWVudCBvbiB0aGUgZmluYWwgUk1TRSB2YWx1ZXMgeW91IG9idGFpbmVkLCBhbmQgd2hhdCB5b3UgbGVhcm5lZCB0aHJvdWdoIHRoZSBjb3Vyc2Ugb2YgdGhpcyBwcm9qZWN0LgoKKipBKio6CkZvciBNb2RlbCA1LCB3ZSBjYW4gZGlyZWN0bHkgY29tcGFyZSB0aGUgYmVzdCB0ZXN0IFJNU0VzOgoKYGBge3Igd2FybmluZz1GQUxTRX0KZ2dwbG90KGRhdGE9ZGZfZXZhbF9tb2Q1LCBhZXMoeD10cmFpbl9zaXplLCB5PXJtc2VzKSkgKwogIGdlb21fbGluZShhZXMoY29sb3I9cm1zZV90eXBlLCBncm91cD1ybXNlX3R5cGUpKSArCiAgZ2d0aXRsZSgnTW9kZWwgNTogVHJhaW5pbmcgYW5kIFRlc3QgUk1TRScpCgpwcmludChldmFsX21vZGVsMSRiZXN0X3Rlc3Rfcm1zZSkKcHJpbnQoZXZhbF9tb2RlbDIkYmVzdF90ZXN0X3Jtc2UpCnByaW50KGV2YWxfbW9kZWwzJGJlc3RfdGVzdF9ybXNlKQpwcmludChldmFsX21vZGVsNCRiZXN0X3Rlc3Rfcm1zZSkKcHJpbnQoZXZhbF9tb2RlbDUkYmVzdF90ZXN0X3Jtc2UpCmBgYAoKVGhlIGltcHJvdmVtZW50IG9mIE1vZGVsIDUgY29tcGFyZWQgdG8gTW9kZWwgNCB3YXM6CgpgYGB7cn0KaW1wcm92ZW1lbnRfaW5fcm1zZV9tb2RlbDUgPSBldmFsX21vZGVsNSRiZXN0X3Rlc3Rfcm1zZSAtIGV2YWxfbW9kZWw0JGJlc3RfdGVzdF9ybXNlCnBlcmNlbnRhZ2VfaW1wcm92ZW1lbnRfaW5fcm1zZV9tb2RlbDUgPSAxLjAgKiBhYnMoZXZhbF9tb2RlbDUkYmVzdF90ZXN0X3Jtc2UgLSBldmFsX21vZGVsNCRiZXN0X3Rlc3Rfcm1zZSkgLyBldmFsX21vZGVsNCRiZXN0X3Rlc3Rfcm1zZQpwcmludCgnSW1wcm92ZW1lbnQgaW4gUk1TRScpCnByaW50KGltcHJvdmVtZW50X2luX3Jtc2VfbW9kZWw1KQoKIyBBcyBhIHBlcmNlbnRhZ2UKcHJpbnQocGVyY2VudGFnZV9pbXByb3ZlbWVudF9pbl9ybXNlX21vZGVsNSkKYGBgCgpOb3RpY2UgdGhhdCB0aGUgaW1wcm92ZW1lbnQgaW4gTW9kZWwgNSB3YXMgYHIge2ltcHJvdmVtZW50X2luX3Jtc2VfbW9kZWw0fWAgd2hpY2ggcmVwcmVzZW50cyBhIGZ1cnRoZXIgYHIge3BlcmNlbnRhZ2VfaW1wcm92ZW1lbnRfaW5fcm1zZV9tb2RlbDUgKiAxMDB9YCBwZXJjZW50IGltcHJvdmVtZW50IG92ZXIgTW9kZWwgNCEgSW4gYWRkaXRpb24sIG5vdGljZSB0aGF0IHRoZSBhbGwtaW4gaW1wcm92ZW1lbnQgb2YgTW9kZWwgNSBjb21wYXJlZCB0byBNb2RlbCAxIGlzIGByIHtldmFsX21vZGVsNSRiZXN0X3Rlc3Rfcm1zZSAtIGV2YWxfbW9kZWwxJGJlc3RfdGVzdF9ybXNlfWAgd2hpY2ggcmVwcmVzZW50cyBhIGByIHsxLjAgKiBhYnMoZXZhbF9tb2RlbDUkYmVzdF90ZXN0X3Jtc2UgLSBldmFsX21vZGVsMSRiZXN0X3Rlc3Rfcm1zZSkgLyBldmFsX21vZGVsMSRiZXN0X3Rlc3Rfcm1zZSAqIDEwMH1gIHBlcmNlbnRhZ2UgaW1wcm92ZW1lbnQhIAoKYGBge3J9CiMgQmVzdCB0ZXN0IFJNU0UKYmVzdF90ZXN0cm1zZV9tb2Q1ID0gZXZhbF9tb2RlbDUkYmVzdF90ZXN0X3Jtc2UKYmVzdF90cmFpbl9zaXplX21vZDUgPSBldmFsX21vZGVsNSRiZXN0X3RyYWluX3NpemUKYGBgCgpUaGUgYmVzdCBtZWFuIHRlc3QgUk1TRSB2YWx1ZSBJIG9ic2VydmVkIGZvciBNb2RlbCA1IHdhcyBgciB7YmVzdF90ZXN0cm1zZV9tb2Q1fWAgYW5kIEkgb2JzZXJ2ZWQgdGhhdCBhdCB0aGUgdHJhaW5pbmcgc2V0IHNpemUgYHIge2Jlc3RfdHJhaW5fc2l6ZV9tb2Q1ICogMTAwfWAgcGVyY2VudC4gTm90aWNlIGFnYWluLCB3ZSBzZWUgYSBzaW1pbGFyIHBhdHRlcm4gYXMgb3RoZXIgbW9kZWxzLiBGcm9tIHRoZSBncmFwaCBhYm92ZSwgbm90aWNlIHRoYXQgZm9yIE1vZGVsIDUgdGhhdCB3ZSBzZWUgdGhhdCBUZXN0IFJNU0UgZ2VuZXJhbGx5IGRlY3JlYXNlcyB3aXRoIGhpZ2hlciB0cmFpbmluZyBzZXQuIEFnYWluLCB0aGlzIG1ha2VzIHNlbnNlIGJlY2F1c2Ugd2l0aCB2ZXJ5IGxvdyB0cmFpbmluZyBzZXQgc2l6ZSwgdGhlIG1vZGVsIHdpbGwgb3ZlcmZpdCB0aGUgbW9yZSBsaW1pdGVkIGRhdGEuCgpUaGlzIHByb2plY3Qgd2FzIGltbWVuc2VseSB1c2VmdWwgaW4gZ2V0dGluZyBleHBlcmllbmNlIGludG8gZGlnZ2luZyBkZWVwZXIgYWJvdXQgYnVpbGRpbmcgbW9kZWxzLiBFdmVuIHdpdGggdGhpcyByZWxhdGl2ZWx5IGxpbWl0ZWQgZGF0YSBzZXQ7IG9uIHRoZSBvcmRlciBvZiAxMDAwcyBvZiBvYnNlcnZhdGlvbnMgYW5kIDEwMHMgb2YgZmVhdHVyZXMsIHRoZXJlIGlzIHByYWN0aWNhbGx5IGluZmluaXRlIHdheXMgd2UgY2FuIGVuZ2luZWVyIG5ldyBmZWF0dXJlcyBhbmQgdGh1cyBkbyBmZWF0dXJlIHNlbGVjdGlvbiBhcyB0byB3aGF0IHdlIHdhbnQgdG8gcHV0IGludG8gb3VyIG1vZGVscy4gCgpPbmUgdGhpbmcgSSBkZWZpbml0ZWx5IGxlYXJuZWQgd2FzIHRoYXQgZmVhdHVyZSBlbmdpbmVlcmluZyBpcyB1c2VmdWwuIEVzcGVjaWFsbHkgd2l0aCBsaW5lYXIgcmVncmVzc2lvbiwgdGhlIHJlbGF0aW9uc2hpcCBiZXR3ZWVuIGFuIG91dGNvbWUgdmFyaWFibGUgYW5kIHBvdGVudGlhbCBmZWF0dXJlcyBpcyB2ZXJ5IHJhcmVseSB0cnVseSBsaW5lYXIuIEV4cGxvcmluZyB3aGljaCB0cmFuc2Zvcm1hdGlvbnMgYW5kIGludGVyYWN0aW9ucyBpbXByb3ZlIHRoZSBtb2RlbCBpcyBpbXBvcnRhbnQuIEhvd2V2ZXIsIHRoaXMgaXMgbm90IHJpc2tsZXNzISEhIEJlY2F1c2Ugb2YgY3Vyc2Ugb2YgZGltZW5zaW9uYWxpdHkgYW5kIGJpYXMvdmFyaWFuY2UgdHJhZGVvZmYsIHlvdSBjYW5ub3QganVzdCBwdXQgd2hhdGV2ZXIgeW91IHdhbnQgaW50byB0aGUgbW9kZWwuIFRodXMsIHRoZSBob2xkLW91dCB0ZXN0IHNldCBpcyBpbXBvcnRhbnQuIAoKQW5vdGhlciB0aGluZyB0aGF0IEkgbGVhcm5lZCBpcyB0aGUgaW1wb3J0YW5jZSBvZiB0cmFpbmluZy90ZXN0IHNwbGl0LCBhbmQgSSBmb3VuZCB0aGUgcmVsYXRpb25zaGlwIHdpdGggc2l6ZSBvZiB0cmFpbmluZyBzZXQgaW50ZXJlc3RpbmcuIFRoZSB0ZXN0IGVycm9yIHNlZW1lZCB0byBnZW5lcmFsbHkgZGVjcmVhc2UgYW5kIGdlbmVyYWxseSB0aGUgbG93ZXN0IHRlc3QgZXJyb3Igd2FzIGZvdW5kIHdoZW4gdHJhaW5lZCBvbiAxMDAlIG9mIHRyYWluaW5nIHNldC4gVGhpcyBpcyBhIGtleSBsZXNzb24gc2luY2UgaWYgeW91IHRyYWluIG9uIHNtYWxsIHNhbXBsZSBzaXplLCB0aGUgbW9kZWwgd2lsbCBvdmVyZml0IHRoZSBzbWFsbCB0cmFpbmluZyBzZXQgKGV2ZW4gd2l0aCBhIHJlbGF0aXZlbHkgaW5mbGV4aWJsZSBtZXRob2QgbGlrZSBsaW5lYXIgcmVncmVzc2lvbiEpIFRodXMsIGFsd2F5cyBiZSBjYXJlZnVsIG9mIG92ZXJmaXR0aW5nIGFuZCBuZXZlciB0cnVzdCB0aGUgdHJhaW5pbmcgZXJyb3IuCgpJZiBJIHdhbnRlZCB0byB0YWtlIHRoaXMgYSBzdGVwIGZvcndhcmQsIEkgd291bGQgZGVmaW5pdGVseSB3YW50IHRvIGluY2x1ZGUgc29tZSBraW5kIG9mIHZhbGlkYXRpb24gc2V0L3Byb2NlZHVyZSAoZS5nLiBLLWZvbGQgdmFsaWRhdGlvbikuIEhvd2V2ZXIsIGdpdmVuIHRoZSBzY29wZSBvZiB0aGlzIHByb2plY3QgYXNzaWdubWVudCBhbmQgdGhpcyBQaWF6emEgcG9zdDogaHR0cHM6Ly9waWF6emEuY29tL2NsYXNzL2o2Z3Q3eWN4Nm5rMTQ1P2NpZD0xMTIzLCBJIGRpZCBub3QgZG8gc28gaGVyZS4gSG93ZXZlciwgdGhpcyB3b3VsZCBiZSBvbmUgdG9vbCB0byBoZWxwIHVzIGZpZ3VyZSBvdXQgdGhlIGJlc3QgZmVhdHVyZXMgdG8gaW5jbHVkZSBpbiBvdXIgbW9kZWwuIEZvciBleGFtcGxlLCB0aGlzIHdvdWxkIGJlIGEgZ29vZCB0b29sIHRvIGhlbHAgdXMgZmlndXJlIG91dCB3aGF0IHBvd2VyIHRyYW5zZm9ybWF0aW9ucyB0byB1c2UuIA==